Briguy37
Briguy37

Reputation: 8412

Java Swing: repaint component during long event

I am working with Java Swing to render my GUI for an application. I have an ActionListener set up on a JButton to fire off a series of tests. Ideally, I would like to update a status area (currently a JTextArea) with real-time testing information and results. In other words, the program would set the text area line-by-line as tests are run and completed to something like:

Running Tests... 
    Test 1:  Check Something...
         Success
    Test 2:  Check Something else...
         Success
    ..    //More testing output

All tests have completed successfully.  Yay.

The problem is that I can't get the status text to update while my actionPerformed event method is still running. This is is all fine and dandy when all of the tests complete quickly. However, some of my tests take an indefinite amount of time to complete (think "database connection timeout"). When this happens, users are stuck with whatever text was already in the status area and my application freezes until the tests complete or fail.

I've tried calling repaint on my JTextArea every time I update the status, but it seems that just adds it to the event queue (which won't get fired till after my actionPerformed event completes...doh). I've also tried calling revalidate and updateUI, and even tried calling wait() on the ActionListener class running my tests, but nothing has worked thus far. Here is the structure of my current code:

..

JTextArea statusOutput_textArea;

..

public void actionPerformed(ActionEvent e) {
    setStatusText("Running Tests...");

    //Run Tests
    int currentTest = 1;

    //Check something
    appendStatusText("        Test " + currentTest++ + ":  Checking Something...");
    ..    //Check Something Here
    appendStatusText("            Success");

    //Check something else
    appendStatusText("        Test " + currentTest++ + ":  Checking Something else...");
    ..    //Check Something Else Here
    appendStatusText("            Success");

    //Other tests
    ..    //Run other tests here

    appendStatusText("\nAll tests have completed successfully.  Yay.");
}//End of actionPerformed method

public void setStatusText (String statusText) {
    statusOutput_textArea.setText(statusText);
    statusOutput_textArea.repaint();
}//End of setStatusText method

public void appendStatusText (String statusTextToAppend) {
    statusOutput_textArea.setText(statusOutput_textArea.getText() + "\n" + statusTextToAppend);
    statusOutput_textArea.repaint();
}//End of appendStatusText method 

Any help would be much appreciated :)

UPDATE

For those interested in the general structure of the solution, here it is:

public class RunTestsButtonActionListener implements ActionListener {
    JTextArea statusOutput_textArea;
    JButton testDatabaseSettings_JButton;

    public RunTestsButtonActionListener(JTextArea statusOutput_textArea){
        this.statusOutput_textArea = statusOutput_textArea;
    }

    public void actionPerformed(ActionEvent e) {
        testDatabaseSettings_JButton = (JButton) e.getSource();

        Thread tests = new Thread(){
            public void run() {
                //Disable button and add tooltip
                testDatabaseSettings_JButton.setEnabled(false);
                testDatabaseSettings_JButton.setToolTipText("Running Tests...");

                //Run Tests
                try {
                    statusOutput_textArea.setText("Running Tests...");
                    int currentTest = 1;

                    //Check something
                    statusOutput_textArea.append("\n        Test " + currentTest++ + ":  Checking Something...");
                    ..    //Check Something Here
                    statusOutput_textArea.append("\n            Success");

                    //Check something else
                    statusOutput_textArea.append("\n        Test " + currentTest++ + ":  Checking Something else...");
                    ..    //Check Something Else Here
                    statusOutput_textArea.append("\n            Success");

                    //Other tests
                    ..    //Run other tests here

                    statusOutput_textArea.append("\n\nAll tests have completed successfully.  Yay.");
                } finally {
                    //Enable button and remove tooltip
                    testDatabaseSettings_JButton.setEnabled(false);
                    testDatabaseSettings_JButton.setToolTipText("");
                }
            }
        };

        tests.start();
    }
}

Upvotes: 2

Views: 2205

Answers (3)

meverett
meverett

Reputation: 921

The problem is you can't have long running tasks happen in an action. As you've noticed, nothing will get updated when this happens.

You should try taking a look at SwingWorker or some other background thread. You would run your tests in the background thread and every time you want to update the UI you would call:

SwingUtils.invokeLater(new Runnable() {
     public void run() {
        appendStatusText(....);
    }
});

Upvotes: 1

Tedil
Tedil

Reputation: 1933

You are doing the checks on "the Swing thread", you would be better of performing those tasks in a different thread (which notifies the GUI if something happens/changes).

Upvotes: 2

Kien Truong
Kien Truong

Reputation: 11381

Run each test in its own thread so they don't block each other. Also, JTextArea already has an thread-safe append method, no need to use setText to simulate append.

Upvotes: 1

Related Questions