Am I updating Swing component outside of EDT?
Asked Answered
G

1

8

I've been reading a lot about Swing, threading, invokeLater(), SwingWorker, etc., but I just can't seem to get my head around it all, so I was trying to create a really simple program to illustrate. I've looked at a lot of examples, but none of them seem to show just what I'm trying to do.

Here's what I'm trying to do in my example. I have a button and a label, and when I click the button, I want the program to pause for 3 seconds before appending a period to the text of the label. During that 3 seconds, I want the GUI to appear as normal and to continue responding to additional clicks. Here's what I wrote:

import javax.swing.SwingWorker;

public class NewJFrame extends javax.swing.JFrame
{
    private javax.swing.JButton jButton1;
    private javax.swing.JLabel jLabel1;

    public NewJFrame()
    {
        initComponents();
    }
    private void initComponents()
    {
        jButton1 = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        jButton1.setText("Button");
        jButton1.addActionListener(new java.awt.event.ActionListener()
        {
            public void actionPerformed(java.awt.event.ActionEvent evt)
            {
                jButton1ActionPerformed(evt);
            }
        });
        getContentPane().add(jButton1, java.awt.BorderLayout.CENTER);
        jLabel1.setText("Text");
        getContentPane().add(jLabel1, java.awt.BorderLayout.PAGE_END);
        pack();
    }

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
    {
        SwingWorker worker=new SwingWorker()
        {
            protected Object doInBackground()
            {
                try{Thread.sleep(3000);}
                catch (InterruptedException ex){}
                return null;
            }
        };
        worker.execute();
        jLabel1.setText(jLabel1.getText()+".");
    }

    public static void main(String args[])
    {
        java.awt.EventQueue.invokeLater(new Runnable()
        {
            public void run(){ new NewJFrame().setVisible(true); }
        });
    }
}

with this code, if I click the button, the period is immediately appended to the label, which makes sense to me, because I am creating and sleeping a background thread, leaving the EDT available to update the label immediately. So I tried this:

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
    Thread thread = new Thread();
    try{ thread.sleep(3000);}
    catch (InterruptedException ex){}
    jLabel1.setText(jLabel1.getText()+".");
}

This almost works except that it blocks the EDT causing the button to turn blue for three seconds before appending the period to the label's text. I don't want the button to look like it's being pressed for the whole three seconds when it was really just clicked quickly, so I tried this:

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
    SwingWorker worker=new SwingWorker()
    {
        protected Object doInBackground()
        {
            try{Thread.sleep(3000);}
            catch (InterruptedException ex){}
            jLabel1.setText(jLabel1.getText()+".");
            return null;
        }
    };
    worker.execute();
}

This appears to work, but aren't I calling jLabel1.setText(...) from the background thread and not the EDT, and therefore breaking the "Swing Single Threading Rule?" If so, is there a better way to achieve the desired effect? If not, can you please explain why?

Guberniya answered 27/2, 2013 at 1:2 Comment(0)
S
4

You're really close...

Try something like this instead.

SwingWorker worker=new SwingWorker()
{
    protected Object doInBackground()
    {
        try{
            Thread.sleep(3000);
        }catch (InterruptedException ex){}
        return null;
    }

    // This is executed within the context of the EDT AFTER the worker has completed...    
    public void done() {
        jLabel1.setText(jLabel1.getText()+".");
    }
};
worker.execute();

You can check to see if you're running in the EDT through the use of EventQueue.isDispatchingThread()

Updated

You could also use a javax.swing.Timer which might be easier...

Timer timer = new Timer(3000, new ActionListener() {
    public void actionPerformed(ActionEvent evt) {
        jLabel1.setText(jLabel1.getText()+".");
    }
});
timer.setRepeats(false);
timer.start();
Stickseed answered 27/2, 2013 at 1:7 Comment(1)
Thanks for the quick and concise answers! That done() method does exactly what I was looking for. The timer is pretty nifty too.Guberniya

© 2022 - 2024 — McMap. All rights reserved.