tags:

views:

444

answers:

3

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);
}
}
A: 

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.)

Tom Hawtin - tackline
Ok, yeah I figured as much. Thanks for the confirmation - I was hoping there'd be a cleaner, less hacky way to do this than forcing the immediate paint but you're quite right: it's linux's painting that's the issue. The thing still bothering me is that the paintImmediately can only be done after showing the frame. Is there no way to paint to the memory buffer **before** the frame is shown?
Mark Silberbauer
Not Linux - it's the fact that Swing isn't thread safe. Swing generally knows when to paint - the issue was the initialization of the components done off the EDT. The problems that can happen in this case can be very strange and hard to diagnose.
Joshua McKinnon
A: 

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.

poindexter
Thanks for help, poindexter. Unfortunately this didn't work on my system.
Mark Silberbauer
You may want to try invalidating the label after you set the text on it. Then pack the frame that the label is in. Honestly this is Swing GTK voodoo.
poindexter
+1  A: 

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.

Joshua McKinnon
Thanks for the response Joshua. You're right in that the construction should be done on the EDT, my e.g. was lazy in that regard. But the problem has nothing to do with the construction of the object. I want to *reuse* the already constructed frame object every time the frame is to appear, instead of reconstructing it, so I simply setVisible(true) when it's needed, setVisible(false) when it's not. This happens in the e.g. when the button is pressed, long after construction. I quickly checked the e.g. using construction wrapped in invokeAndWait (on EDT) and the prob is still there as expected.
Mark Silberbauer
Interesting - reproduction of the issue went away for me once I constructed the JFrame on the EDT. The re-use is totally fine, as is the setVisible call which is already on the EDT. Maybe give upgrading your JRE a try?
Joshua McKinnon
Thanks for testing this - I'll give it a shot.
Mark Silberbauer