I am writing a GUI with a button. When the user clicks the button, I would like a "Beginning work..." message to appear in a JTextArea immediately, and a "Finished." message to appear when the work is done. The GUI contains some code of the form
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {
myJTextArea.append("Beginning work...\n");
myJTextArea.append("Finished.\n");
}
Unfortunately, neither message appears until the end. Is there a way to flush messages to JTextArea? On another forum, I saw people mention running a separate thread for the JTextArea output. Would some solution based on that be possible?
Thanks
解决方案
Unfortunately, neither message appears until the end. Is there a way to flush messages to JTextArea? On another forum, I saw mention running a separate thread for the JTextArea output. Would some solution based on that be possible?
This has nothing to do with "flushing" the JTextArea, and all to do with making sure that your code follows Swing threading rules. The long running code that is outputting to the JTextArea should be called in a background thread such as with a SwingWorker. Then it would intermittently output its results to the JTextArea, but making sure to do so carefully on the Swing event thread. If you use a SwingWorker, this could be done using the publish/process method pair.
For example:
import java.util.List;
import javax.swing.*;
public class SwingThreadingEg extends JPanel implements MyAppendable {
private JTextArea area = new JTextArea(30, 50);
public SwingThreadingEg() {
JScrollPane scrollPane = new JScrollPane(area);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
add(scrollPane);
}
@Override
public void append(String text) {
area.append(text);
}
private static void createAndShowGui() {
SwingThreadingEg mainPanel = new SwingThreadingEg();
MyWorker myWorker = new MyWorker(mainPanel);
// add a Prop Change listener here to listen for
// DONE state then call get() on myWorker
myWorker.execute();
JFrame frame = new JFrame("SwingThreadingEg");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class MyWorker extends SwingWorker {
private static final long SLEEP_TIME = 500;
private MyAppendable myAppendable;
public MyWorker(MyAppendable myAppendable) {
this.myAppendable = myAppendable;
}
@Override
protected Void doInBackground() throws Exception {
publish("Beginning Work");
// simulate some long-running task:
for (int i = 0; i < 20; i++) {
publish("From SwingWorker: " + i);
Thread.sleep(SLEEP_TIME);
}
publish("Finished!");
return null;
}
@Override
protected void process(List chunks) {
for (String text : chunks) {
myAppendable.append(text + "\n");
}
}
}
interface MyAppendable {
public void append(String text);
}
Code adapted from my code from an answer to a previous similar question.
Note, if you're going to use a standard Runnable and a background Thread without a SwingWorker, as Lars recommends, you'll first of all want to implement a Runnable and not extend Thread, and you must take care that most all Swing calls are queued onto the event thread, thus something like:
@Override
public void actionPerformed(ActionEvent e) {
textarea.append("Start...");
new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
textarea.append("End");
}
});
}
}).start();
}
This looks a bit complicated what with all the nested anonymous inner classes, and in fact this is one of the main reasons why a SwingWorker may work better in this situation, since the code used is simpler, reducing the chances of creating unseen bugs.