Swing: Changing a label on hidden frame and then showing the frame happens in reverse order on EDT

3.3k views Asked by At

The Problem

I create a dialog box in swing (JRE 6 update 10, Ubuntu linux). When the user has finished using the dialog box it is hidden. When the users click on a button in another frame, a label on the box is changed according to the button, and then the box is shown again.

The problem I'm having is that the box is shown before the label changes even though, programatically, I'm making the calls in the opposite order. This causes the box to appear followed by the label change which looks "glitchy" on our slow target HW. It appears that the EDT schedules the frame setVisible(true) ahead of the label setText(....); it gives priority to this call. Is there any way to get the EDT to schedule the setVisible(true) to execute after the setText(....)?

Note that the code is called from a button click which is already executing on the EDT so one can't use SwingUtilities.invokeAndWait. I've tried using the invokeLater method but the EDT still re-schedules it.

To reproduce

Run the following code in an IDE in debug mode and break in the showButton's action code after showing and hiding the "dialog" frame. The label's setText(....) change will not have an immediate effect on the GUI but the frame's setVisible(true) will. Then step through the EDT and you'll see that the setText eventually happens further down the EDT schedule.

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;

public class DemonstrateFramePaintEDTPriority {

static class MyFrame extends JFrame {

    private JFrame frame;
    private JLabel label;
    int i = 0;

    public MyFrame() {
        // Some label strings
        final String string[] = new String[] { "label text one",
                "label 2222222", "3 3 3 3 3 3 3" };

        // Create GUI components.
        frame = new JFrame("Dialog");
        label = new JLabel("no text set on this label yet");
        frame.setSize(500, 200);
        frame.setLayout(new FlowLayout());
        frame.add(label);

        // Add show and hide buttons.
        JButton showButton = new JButton("show dialog");
        showButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // Set the label text - THIS HAPPENS AFTER frame.setVisible
                label.setText(string[i]);

                // Select new label text for next time.
                i++;
                if (i >= string.length) {
                    i = 0;
                }

                // Show dialog - THIS HAPPENS BEFORE label.setText
                frame.setVisible(true);
            }

        });

        JButton hideButton = new JButton("hide dialog");
        hideButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                label.setText("label removed");
                frame.setVisible(false);
            }

        });
        setSize(500, 200);
        setLayout(new FlowLayout());
        add(showButton);
        add(hideButton);
    }
}

public static void main(String[] args) {
    JFrame frame = new MyFrame();
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    frame.setVisible(true);
}
}
3

There are 3 answers

2
Tom Hawtin - tackline On

The problem isn't that the label component's text hasn't change. It's that the repaint has been scheduled but hasn't happened yet. That and Linux has a tendency of being extremely slow to open windows (a problem with window manager or similar?). The java.awt.EventQueue schedules by priority, although I can't remember the details.

JComponent.paintImmediately looks like a likely method. You might want to find a (good) text on animation within Swing/AWT. (That or run without a window manager.)

2
poindexter On

I just encountered a similar problem on OS X. My solution was:

frame.invalidate()
frame.pack()
frame.setVisible(true)

This appears to force swing to repaint the frame in memory before displaying it.

3
Joshua McKinnon On

I don't think this is an issue in Linux painting. I can reproduce your issue on Windows 7 64-bit (JDK 1.6.0_18 (early access)). You're very close to the answer - you know about SwingUtilities.invokeLater, but you aren't thinking about using it where you need to.

Say it with me:

You must initialize and modify Swing components on the EDT

If you don't, bad things will happen. In this case, your weird repaint behavior is a result of not creating your JFrame and the contained components on the EDT. If you wrap this line in a SwingUtilities.invokeLater, it will fix your issue:

JFrame frame = new MyFrame();

You are correct - your setText is happening on the EDT, but the initialization of the component itself didn't happen on the EDT, and that is the root cause.

When in doubt on whether given code happens on the EDT, you can use SwingUtilities.isEventDispatchThread() to find out.

I highly recommend reading Filthy Rich Clients if you plan on doing a lot of Swing development. I am in the process of reading it now.