Reputation: 133
I have written some Java Swing code that collects some input, does some calculations, and then displays the result.
Expected result:
Actual result:
I am pretty new to Swing, and I have been trying to get to the bottom of this without much luck. I have narrowed down the error to a specific line of the code. If I comment out the line
JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION);
in the code, then the spinner appears the first time through as expected. If I leave the line in, then I have the problem as described.
Please note that I have heavily trimmed this sample down to eliminate a lot of the U/I flow that is in the actual program, but this sample does break as described. I was not able to simplify my sample code further.
The simplified code
package ca.pringle.maze.ui;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import ca.pringle.maze.logic.Edge;
import ca.pringle.maze.logic.MazeConfig;
import ca.pringle.maze.logic.MazeMaker;
import ca.pringle.maze.logic.PathFinder;
import ca.pringle.maze.util.Pair;
public final class MazeDrawer extends JFrame {
private final MazePanel mazePanel;
public MazeDrawer() {
mazePanel = new MazePanel();
}
public void init() {
final JScrollPane scrollPane = new JScrollPane(mazePanel);
final JMenuBar menuBar = createMenuBar((actionEvent) -> generateNewMaze());
setJMenuBar(menuBar);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(scrollPane, BorderLayout.CENTER);
setSize(100, 100);
setVisible(true);
repaint();
}
void generateNewMaze() {
// if I comment out this line below, then the spinner appears on the first run as expected.
// if I leave this line in, then the spinner does not appear on the first run, only on subsequent runs
JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION);
final MazeConfig mazeConfig = new MazeConfig(1000, 1000, 1);
final MazeMaker mazeMaker = new MazeMaker(mazeConfig);
final PanelDimensions panelDimensions = new PanelDimensions(mazeConfig.getRows(), mazeConfig.getColumns(), 15, 15);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
// the two lines below can take a long time depending on the inputs. I tried removing
// them and replacing with a sleep to simulate and simplify the problem, but no luck,
// so I left them in, even if I did not include the code.
final Set<Edge> edges = mazeMaker.generateUndirectedMazeEdges();
final Pair<Integer, Integer> startAndEndNodes = new PathFinder().findLongestPath(edges, mazeConfig);
mazePanel.update(edges, startAndEndNodes, panelDimensions);
mazePanel.setPreferredSize(new Dimension(panelDimensions.panelWidth, panelDimensions.panelHeight));
mazePanel.repaint();
setSize(Math.min(1200, panelDimensions.panelWidth + 11), Math.min(700, panelDimensions.panelHeight + 53));
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
private JMenuBar createMenuBar(final ActionListener menuListener) {
final JMenuBar bar = new JMenuBar();
final JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
final JMenuItem newMenuItem = new JMenuItem("New", KeyEvent.VK_N);
newMenuItem.addActionListener(menuListener);
fileMenu.add(newMenuItem);
bar.add(fileMenu);
return bar;
}
}
Any help is appreciated. Please assume I am a complete Swing noob, so a more specific answer/explanation is more likely to help than a high level one that assumes I know something about Swing. I will read any suggested documentation of course.
If there are any problems or clarifications needed, please let me know, I am happy to update the post as many times as required.
Upvotes: 0
Views: 498
Reputation: 9108
I suspect your init()
and generateNewMaze()
are being called from another thread and giving unpredictable results. You should never call swing code or manipulate swing objects from outside the EventDispachThread. If you aren't in fact calling from another thread, you don't need the outer invokelater wrapper.
Here's a better way to do what you are trying to do:
void generateNewMaze() {
SwingUtilities.invokeLater(() -> {
JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
mazePanel.setEnabled(false);
new Thread(() -> {
try {
// Do the long running maze task; this sleep is to simulate that task
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
mazePanel.setEnabled(true);
});
}).start();
});
}
You might also consider using the Glass pane: Wait cursor and disable java application
Upvotes: 1