Early Events Before AWT/Swing Realization?

On Tuesday, Dr. Nguyen asked me to help answer one of his students’ questions. In the first lecture on Java GUIs, a student asked if we shouldn’t do the entire creation of the GUI in the event thread, using SwingUtilities.invokeLater.

Both Dr. Nguyen and I looked through the code and decided that what we were doing was fine, even though Sun’s official stance is that GUI creation should happen in the event thread. However, as far as I knew, events can really only begin to trickle in once the GUI has been realized, by using setVisible(true) or pack(), for example. Since the main thread dies immediately after realization, there cannot be any undesirable interaction due to concurrency.

Yesterday, I sat in Dr. Nguyen’s COMP 212 class and testified this and further explained the notion of realization. As the class moved on, though, I looked through the listeners that one can install on a JFrame, and I noticed three listeners in particular: ComponentListener, PropertyChangeListener, and ContainerListener. I was worried that these listeners might be invoked even before realization, so I modified my statement to the class that what Dr. Nguyen was doing was fine, but that there are some things during GUI initialization that have to be done in the event thread.

Today I took the time to experiment a bit, and I wrote the following program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.beans.*;

public class EarlyEvent {
  public static void main(String[] args) {
    JFrame f = new Frame1("A JFrame with 2 JButtons");
    f.setVisible(true);
  }  
  private static abstract class AFrame extends JFrame {
    public AFrame(String title) {
      super(title);
      addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(java.awt.event.WindowEvent e) {
          System.exit(0);
        }
      });
      addComponentListener(new ComponentListener() {
        public void componentHidden(ComponentEvent e) {
          System.out.println(Thread.currentThread().getName()+
            ": Invoked when the component has been made invisible.");
        }
        public void componentMoved(ComponentEvent e) {
          System.out.println(Thread.currentThread().getName()+
            ": Invoked when the component's position changes.");
        }
        public void  componentResized(ComponentEvent e) {
          System.out.println(Thread.currentThread().getName()+
            ": Invoked when the component's size changes.");
        }
        public void componentShown(ComponentEvent e) {
          System.out.println(Thread.currentThread().getName()+
            ": Invoked when the component has been made visible.");
        }
      });
      addPropertyChangeListener("background", new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          System.out.println(Thread.currentThread().getName()+
            ": This method gets called when a bound property is changed.");
        }
      });
      getContentPane().addContainerListener(new ContainerListener() {
        public void componentAdded(ContainerEvent e) {
          System.out.println(Thread.currentThread().getName()+
            ": Invoked when a component has been added to the container.");
        }
        public void componentRemoved(ContainerEvent e) {
          System.out.println(Thread.currentThread().getName()+
            ": Invoked when a component has been removed from the container.");
        }
      });
      initialize();
    }
    protected abstract void initialize();
  }  
  private static class Frame1 extends AFrame {
    public Frame1(String title) {
      super(title);
    }
    protected void initialize() {
      Container cp = getContentPane();
      cp.setLayout(new FlowLayout());
      JButton jb1 = new JButton("Button 1");
      JButton jb2 = new JButton("Button 2");
      cp.add(jb1);
      cp.add(jb2);
      setSize(300, 300);
      setLocation(200, 200);
      System.out.println(Thread.currentThread().getName()+": pack or setVisible");
      //pack();
      setVisible(true);
    }
  }
}

It is heavily based on the listings given in Dr. Nguyen’s GUI lecture, except all packaged into a single file. Because I was still trying to make it as similar as possible to the listings COMP 212 uses, since this might serve as a demonstration for the class, the listing is longer and a bit more convoluted than it would have to be just to prove the point.

What I’m doing in the AFrame constructor is add the aforementioned listeners before GUI construction actually takes place in the initialize() method. The listeners print out what happened, prefixed by the name of the thread in which the listener method is executed. Finally, right before calling the pack() method, which causes realization, I print the thread name (which I know is the main thread) and "pack or setVisible".

What I feared was that listeners would be invoked before I see "pack or setVisible" being printed, and that the code would be running in the event thread already, disproving my assumption that before realization there would only be the main thread.

However, that does not seem to be the case. The ComponentListener is only executed after realization, and while the PropertyChangeListener and the ContainerListener get executed before realization, the code runs in the main thread. Also, interestingly, the request to reposition the frame gets delayed until after realization. Here is the output from the program:

main: Invoked when a component has been added to the container.
main: Invoked when a component has been added to the container.
main: This method gets called when a bound property is changed.
main: pack or setVisible
AWT-EventQueue-0: Invoked when the component's size changes.
AWT-EventQueue-0: Invoked when the component's position changes.
AWT-EventQueue-0: Invoked when the component has been made visible.

Everyone should know that writing multithreaded GUI programs is difficult, and that care needs to be taken, so Sun’s advice to serialize access to, and particularly mutation of, the GUI by using SwingUtilities.invokeLater. However, I still have to encounter an example where not doing this does actually cause a problem.

Share

About Mathias

Software development engineer. Principal developer of DrJava. Recent Ph.D. graduate from the Department of Computer Science at Rice University.
This entry was posted in Graduate School, Research. Bookmark the permalink.

Leave a Reply