views:

226

answers:

3

I have some code which takes a few minutes to process, it has to connect to the web for each string in a long array, each string is a url. I want to make it so that everytime it connects, it should refresh the jtextarea so that the user is not staring into a blank page that looks frozen for 20 min. or however long it takes. here is an example of something i tried and didnt work:

try {
            ArrayList<String> myLinks = LinkParser.getmyLinksArray(jTextArea1.getText());
            for (String s : myLinks) {
                jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
            }
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(jTextArea1, "Parsing Error", "Parsing Error", JOptionPane.ERROR_MESSAGE);
            Logger.getLogger(MYView.class.getName()).log(Level.SEVERE, null, ex);
        }
A: 

swing/awt is a single threaded library, so once a component is shown, just changing it's appearance won't work correctly. You need to change the component on the GUI Thread, not from your thread. To do this wrap any code that updates a component with SwingUtilities.invokeLater... as in

SwingUtilities.invokeLater(new Runnable()
{
    public void run()
    {
        jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
    }
});

also you want to limit what you do on the gui thread to avoid the gui from becoming sluggish, so if checkFileStatus is time consuming, execute it outside the run method and store the result in a final local variable, and just access the variable in the run() code.

MeBigFatGuy
Ok, thank you, i will try this now :-)
Robert
in what package is SwingUtils?
Robert
sorry javax.swing.SwingUtilities
MeBigFatGuy
+2  A: 

The problem is that you need to perform the computation asynchronously. You should create a background thread that performs the computation, and then use SwingUtilities.invokeLater to update the JTextArea.

final ArrayList<String> myLinks = //...
(new Thread()
{
    public void run(){
        for (String s : myLinks) {
            try{
               final String result = LinkChecker.checkFileStatus(s) + "\n";
               SwingUtilities.invokeLater(new Runnable(){ 
                    public void run(){    
                      jtextArea2.append(result);
                    }
                });
             }catch(IOException error){
                // handle error
             }
        }
    }
}).start();

Edit
It has been pointed out that JTextArea's append function actually is thread safe (unlike most Swing functions). Therefore, for this particular, case it is not necessary to update it via invokeLater. However, you should still do you processing in a background thread so as to allow the GUI to update, so the code is:

final ArrayList<String> myLinks = //...
(new Thread()
{
    public void run(){
        for (String s : myLinks) {
            try{
               jtextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
             }catch(IOException error){
                // handle error
             }
        }
    }
}).start();

However, for pretty much any other operation that modifies a Swing object, you will need to use invokeLater (to ensure the modification occurs in the GUI thread), since almost all the Swing functions aren't thread safe.

Michael Aaron Safyan
Thanks, I have everything in place, but its telling me that s need to be final, so it should be for (final String s : mylinks) instead?
Robert
For the example above, myLinks would need to be final.
Michael Aaron Safyan
But if you have the for-loop outside of the Thread, then you would need to make s final (create final fs = s; and use fs).
Michael Aaron Safyan
Got it, it works amazingly, thanks!
Robert
would i be able to but all my code within the run method?
Robert
@Robert, you can do whatever computation you want in the Thread's run method, but if you modify any Swing components, you will need to use SwingUtilities.invokeLater with a Runnable object that performs the manipulation, because Swing objects are not thread-safe and can only be safely modified from the main GUI thread (which is what invokeLater does).
Michael Aaron Safyan
The append(...) method is thread safe (according to the API documentation for the method), so in this example you should not need to use invokeLater.
camickr
+1 for pointing out that append() is EDT safe - most folks forget to check the API docs on that one.
Kevin Day
@camickr, good call... didn't realize that. I will update my answer.
Michael Aaron Safyan
+1  A: 

You need to investigate threading and its relationship to GUI updates in Swing. Anything that affects or makes use of GUI components in Swing must done on a special thread called the Event Dispatch Thread (EDT).

If your code snippet, if it's freezing the GUI, I imagine that it is being run in the EDT. Performing a long-running action on the EDT will make the GUI unresponsive, because no further updates can be done while your long-running process is using the thread.

There is a helper class called SwingWorker that allows you to offload long-running computations to a background thread, and then make updates to the GUI thread when it is complete. The SwingWorker looks after the context switches between the GUI thread and the background thread. You can also display progress bars to let the user know the state of the long-running process, so they know your application hasn't hung.

Ash
Thanks. The above answers made it so I could get it working now, but I think for the long run I should look into this and understand how it works.
Robert