How do I flush to Java's JTextArea?

你离开我真会死。 提交于 2019-12-24 03:54:08

问题


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");

    <more lines of code>

    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


回答1:


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.

Have a look at: Tutorial: Concurrency in Swing.

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<Void, String> {
    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<String> 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.




回答2:


Another example without a Worker, just using Thread:

package swingexperiments;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class SwingExperiment {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }

    private static void createAndShowGui() {
        JFrame jFrame = new JFrame("Test");
        jFrame.setLayout(new BorderLayout());
        JTextArea textarea = new JTextArea();
        jFrame.add(textarea, BorderLayout.CENTER);
        jFrame.add(new JButton(new AbstractAction("Test") {
            @Override
            public void actionPerformed(ActionEvent e) {
                textarea.append("Start..."); // here we are on the event dispatcher thread
                new Thread() {
                    @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();
            }
        }), BorderLayout.SOUTH);
        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jFrame.pack();
        jFrame.setSize(1000, 800);
        jFrame.setVisible(true);
    }

}


来源:https://stackoverflow.com/questions/32363796/how-do-i-flush-to-javas-jtextarea

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!