Should swing event handlers be queued after the event on the EDT?

783 views Asked by At

Should swing event handling code be queued after the event on the EDT? If so, is it the responsibility of the event source to schedule the event handlers, or is it the responsibility of the event handler to schedule the actual handling code later?

Consider:

JButton b = new JButton();
b.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e){
        /*
         * SNIP 1
         * do something that may affect other
         * components or generate new events...
         */

        // SNIP 2
        EventQueue.invokeLater(new Runnable(){
            public void run(){
                /*
                 * do something that may affect other
                 * components or generate new events...
                 */
            }
        });
    }
});

SNIP 1 runs handler code when the event is received. If the JButton takes responsibility for scheduling event by EventQueue.invokeLater(), then this is probably correct. SNIP 2 receives the event, and takes responsibility of scheduling handler code after the event is received (and handled by all other listeners for this specific event type). Which is correct?

EDIT: Just for clarity, I want to schedule event handlers later on EDT because changing state in the first event handler may hide original state at the time of the event from other event handlers for the component.

3

There are 3 answers

0
Jesse On BEST ANSWER

I know its probably bad form to answer my own question, but I've come to the conclusion that one should queue event handlers after an event in Swing. The reason is this: each component can have more than one listener of a given type. You don't know what listeners are listening on any given component (swing itself may add listeners that you are unaware of), and you don't know what assumptions they make about the component tree, and the state of the tree when they handle an event.

Now, because each component can have multiple event handler for a certain event type, we don't know which event handler will be invoked first. If my event handler modifies the component tree, a subsequent event handler for the same event type will not see the component tree as it was at the time of the event. Instead, it will see the component tree as it was after my changes. The same problem exists for other application state which may be inspected and modified by two separate listeners of the same type on the same component. This is surely not right.

Secondly, there is a concept of events being dispatched by the EDT in a sequential order. ie. If event x happened before event y in real time, then the event handlers for x should run before the event handlers for y. Now, if my component, lets call it c1, has 2 event handlers for event actionPerformed() - eh1 and eh2. When actionPerformed() is fired on c1, eh1 is invoked to handle the event. It then modifies component c2, and causes a itemStateChanged() event to be fired on c2. Because this change queued after the first actionPerformed() event was complete (later on the EDT), and because c2 does not queue the itemStateChanged() event to run later on the EDT, the itemStateChanged() event is handled by all its listeners. Only then does the second actionPerformed() listener (eh2) get to handle the original actionPerformed() event.

If eh2 was also a listener to itemStateChanged() events on component c2, then it would appear to eh2 that the itemStateChanged() event actually happened before the actionPerformed() event. This too is surely not right.

So that answers question 1. Yes, event handling code, which modifies application state or the component tree should be scheduled for execution after the event is processed. The event handlers should inspect whatever state they need to inspect at the time of the event, but react to the event (make changes) later on the EDT, by calling EventQueue.invokeLater().

The second part of my question is about whose responsibility it is to ensure that handling of events happens after the event itself has taken place. In hindsight, this is a stupid question. The same problem exists if the event handlers are executed immediately when an event occurs, or if they are executed later by the component calling EventQueue.invokeLater().

Incidentally, while I was researching this, I saw that not all Swing component do this uniformly. For example, JCheckBox fires the itemStateChanged() events immediately when the event occurs, while JComponent fires componentResized() (via is super class java.awt.Component) later on the EDT.

So it seems that the most robust way to handle events is to first inspect your state to make decisions about what actions you will take, then perform those actions later on the EDT. Any thoughts? I'd love to hear other people's insight into this.

2
trashgod On

As @HFOE notes, elements in the EventQueue are sequential and ordered; SNIP 2 shouldn't be required. If I understand your requirement, one approach might be to forward the event to any other interested listener(s), as suggested in this question & answer.

2
Hovercraft Full Of Eels On

Please let me know though if I am misunderstanding your question, but all code in the Swing event Listeners is by run on the EDT, so there is no need to queue anything on to the EDT. I have only rarely seen code such as in snip2 used in a Swing listener, and usually that's if the listener calls an anonymous inner Runnable in a background thread.