Ok, this is a follow-up question to my question from yesterday, "Error handling in SwingWorker."
In consideration of the fact that it might be ok to call SwingUtilities#invokeAndWait()
inside of SwingWorker#doInBackground()
, I want to push it a step further and ask: might it also be ok to call SwingUtilities#invokeLater()
inside the background thread?
Another SSCCE to illustrate:
public class NewClass extends javax.swing.JFrame {
public NewClass() {
jScrollPane1 = new javax.swing.JScrollPane();
atable = new javax.swing.JTable();
jButton1 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
atable.setModel(new javax.swing.table.DefaultTableModel(
new Object[][]{
{null, null},
{null, null},
{null, null},
{null, null}
},
new String[]{
"Title 1", "Title 2"
}
));
jScrollPane1.setViewportView(atable);
jButton1.setText("Go");
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addComponent(jButton1)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 228, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButton1)
.addContainerGap())
);
pack();
setLocationByPlatform(true);
setVisible(true);
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
new Task();
}
class Task extends javax.swing.SwingWorker<Void, Object[]> {
DeterminateLoadingFrame dlf;
public Task() {
dlf = new DeterminateLoadingFrame();
atable.setModel(new javax.swing.table.DefaultTableModel(
new Object[][]{},
new String[]{
"Title 1", "Title 2"
}
));
dlf.setMaximum(1000);
execute();
}
@Override
protected void process(java.util.List<Object[]> chunks) {
for (Object[] row : chunks) {
((javax.swing.table.DefaultTableModel) atable.getModel()).addRow(row);
}
}
@Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < 1000; i++) {
final int j = i;
Object[] row = new Object[2];
row[0] = "row " + i;
row[1] = Math.round(Math.random() * 100);
publish(row);
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
dlf.changeText("Processing " + j + " of " + 1000);
dlf.setValue(j);
}
});
Thread.sleep(100);
}
return null;
}
@Override
protected void done() {
if (!isCancelled()) {
try {
get();
} catch (java.util.concurrent.ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
dlf.dispose();
}
}
class DeterminateLoadingFrame extends javax.swing.JFrame {
javax.swing.JLabel jLabel1;
javax.swing.JProgressBar jProgressBar1;
public DeterminateLoadingFrame() {
jProgressBar1 = new javax.swing.JProgressBar();
jLabel1 = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setTitle("Loading");
jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
jLabel1.setText("Loading ...");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 182, Short.MAX_VALUE)
.addComponent(jProgressBar1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 182, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
setAlwaysOnTop(true);
setLocationRelativeTo(null);
setVisible(true);
setAlwaysOnTop(false);
}
public void changeText(String message) {
this.jLabel1.setText(message);
System.out.println(message);
}
public void setMaximum(int max) {
jProgressBar1.setMaximum(max);
}
public void setValue(int n) {
jProgressBar1.setValue(n);
}
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new NewClass();
}
});
}
private javax.swing.JTable atable;
private javax.swing.JButton jButton1;
private javax.swing.JScrollPane jScrollPane1;
}
In this SSCCE, we are updating the DeterminateLoadingFrame
to update the user about what is happening, while publishing the results through publish()
and process()
.
This seems as unorthodox as the original question asked yesterday. Yet I am unable to come up with any reason it would be not a good idea. We are adhering to proper threading policies. And updating the GUI in this way would seem (to me) to be incredibly useful and versatile. Much moreso, in fact, than process()
and publish()
.
Further: accomplishing what the SSCCE is illustrating would be harder strictly using process()
and publish()
because how would you pass the value of i
to the process()
method?
It's correct, but it doesn't benefit from all the work done by the SwingWorker to batch updates and let you avoid using these low-level SwingUtilities methods. It also couples the background process and the UI updates instead of decoupling them elegantly.
How to pass
i
to the publish method: create a class containing this counter and the row: