I'm running a simulation which times the movement of an objects reaching a destination, and then displays the results on a JTable after the simulation ends. However, when the window containing JTable is closed, I want to be able to have the main class run again.
I was thinking of using frame.setDefaultCloseOperation(); somehow, but I'm unsure of how to do this.
Here is what my code currently looks like:
public class Main {
public static void main(String[] args) throws InterruptedException {
Scanner scan = new Scanner(System.in);
ArrayList<Integer> inputsA = new ArrayList<>();
ArrayList<Integer> timesA = new ArrayList<>();
System.out.println("Please enter the time for A: ");
int inputA = scan.nextInt();
inputsA.add(inputA);
Simulation sim = new Simulation(inputA);
sim.runSim();
timesA.add(sim.getA());
int length = timesA.size();
Object[][] tableData = new Object[length][2];
for (int i = 0; i < length; i++) {
tableData[i] = new Object[]{inputsA.get(i), timesA.get(i)};
}
JFrame frame = new JFrame();
String[] tableColumn = {"Input A", "Time A"};
TableModel model = new DefaultTableModel(tableData, tableColumn) {
public Class getColumnClass(int column) {
Class returnValue;
if ((column >= 0) && (column < getColumnCount())) {
returnValue = getValueAt(0, column).getClass();
} else {
returnValue = Object.class;
}
return returnValue;
}
};
RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(model);
JTable table = new JTable(tableData, tableColumn);
table.setBounds(100, 55, 300, 200);
table.setRowSorter(sorter);
JScrollPane jScrollPane = new JScrollPane(table);
frame.add(jScrollPane);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
One solution is to
frameSetDefaultCloseOperation(JFame.DISPOSE_ON_CLOSE);
to prevent closing JFrame from closing the JVM. But this is the default setting for JFrames, and so this isn't needed.This solution is simple, straightforward, easy, and wrong, because it does not address the glaring issues with your code, and those include:
Instead, I recommend that you
For example,
import java.awt.BorderLayout;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class Main02 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Simulation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SimPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class SimPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final String[] COLUMN_NAMES = { "Input A", "Time A" };
private JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
private JButton button = new JButton("Run Simulation");
private DefaultTableModel model = new DefaultTableModel(COLUMN_NAMES, 0);
private JTable table = new JTable(model);
public SimPanel() {
JPanel inputPanel = new JPanel();
inputPanel.add(new JLabel("Time for A: "));
inputPanel.add(spinner);
inputPanel.add(button);
button.addActionListener(e -> {
int inputA = (Integer) spinner.getValue();
Simulation sim = new Simulation(inputA);
sim.runSim();
model.addRow(new Object[] { inputA, sim.getA() });
});
setLayout(new BorderLayout());
add(inputPanel, BorderLayout.PAGE_START);
add(new JScrollPane(table), BorderLayout.CENTER);
}
}
An improvement would be to run the simulation from within a background thread, such as a SwingWorker so that any time delay caused by the simulation does not freeze the GUI.
The code explained:
First of all, any Swing GUI code should be called on a specific thread, the Swing event thread, also known as the EDT for "Event Dispatch Thread". Doing this helps ensure that the Swing code has lower risk of throwing unpredictable and hard to debug thread-induced errors.
One way to do this is to put the code that creates and runs the GUI within a Runnable that is called inside of SwingUtilities.invokeLater(runnable-goes-here)
:
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Simulation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SimPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
Next, I create a class, SimPanel that either extends or can return a JPanel, one that can easily be placed into the main window (JFrame) or in another JPanel, or anywhere I desire.
class SimPanel extends JPanel {
This array, COLUMN_NAMES
, will be passed into my DefaultTableModel's constructor to create the table's column headers:
private static final String[] COLUMN_NAMES = { "Input A", "Time A" };
// ...
private DefaultTableModel model = new DefaultTableModel(COLUMN_NAMES, 0);
I get my input from a JSpinner and not a JTextField because with a JSpinner, the user cannot enter non-numeric input. It's not possible:
private JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 100, 1));
The JButton starts the simulation and the JTable displays the simulation results which is all pushed into its table model:
private JButton button = new JButton("Run Simulation");
private JTable table = new JTable(model);
In the SimPanel constructor, I create a JPanel for the user to input their data. It holds both the JSpinner and JButton:
JPanel inputPanel = new JPanel();
inputPanel.add(new JLabel("Time for A: "));
inputPanel.add(spinner);
inputPanel.add(button);
The butotn's ActionListener gets the user's input from the spinner, creates a new Simulation object with that input, runs the simulation, and passes the result to the table model:
button.addActionListener(e -> {
int inputA = (Integer) spinner.getValue();
Simulation sim = new Simulation(inputA);
sim.runSim();
model.addRow(new Object[] { inputA, sim.getA() });
});
Again, ideally, the simulation would be run within a SwingWorker, but that is not being done here for simplicity's sake.
I here make the SimPanel's layout a BorderLayout. I then add the input panel to the top of the sim JPanel, and add the JTable, held in a JScrollPane, in the center position. Note that I never set the size of a JTable, since that will break the table's ability to expand with new data that might extend beyond the viewport size of the scrollpane that holds it:
setLayout(new BorderLayout());
add(inputPanel, BorderLayout.PAGE_START);
add(new JScrollPane(table), BorderLayout.CENTER);