Swing in a better world: Listeners
In the list of the things that needs improving in Swing, the implementation of listeners takes the first place. The problem is the fact that the order in which listeners are notified is not specified and it is not even guaranteed that your listeners will be notified after the Swing system listeners. Actually all listeners can be mixed together with any possible combination and it leads to two main problems:
- Listeners' order is messed up after updateUI() is called. See 4871932 and 6508168
- User's listeners are notified before the UI listeners See 4178930, 6294075, 4675786, 4688560, 5032759, 4816818 and 4730055
Examining the problem
Let's write some code and check if a JButton is in the pressed state when MouseListener.mousePressed() is notified:
import javax.swing.*;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class ListenersOrder extends JFrame {
public ListenersOrder() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
final JButton button = new JButton("JButton");
button.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
System.out.println("button.getModel().isPressed() = "
+ button.getModel().isPressed());
}
});
add(button);
pack();
}
public static void main(String... args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ListenersOrder().setVisible(true);
}
});
}
}
When you press the button you can see that it is in the pressed state, as we may expect.
button.getModel().isPressed() = true
Let's add some more code and change the system Look and Feel after the button is created:
import javax.swing.*;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class ListenersOrder extends JFrame {
public ListenersOrder() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
final JButton button = new JButton("JButton");
button.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
System.out.println("button.getModel().isPressed() = "
+ button.getModel().isPressed());
}
});
add(button);
pack();
changeLaF();
}
private void changeLaF() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
// we don't forget to update all the components
SwingUtilities.updateComponentTreeUI(this);
}
public static void main(String... args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ListenersOrder().setVisible(true);
}
});
}
}
This simple update changes the mouse listener's order and now, surprisingly, when you press the button you'll see:
button.getModel().isPressed() = false
How to explain it?
Let's examine the mouse listeners' added to the button, corresponding to every relevant line:
| Line of code | MouseListener added to the button |
| JButton button = new JButton("JButton"); | Listener from MetalLaF |
| button.addMouseListener(new MouseAdapter() {}); | user's listener |
| changeLaF(); | |
| changeLaF(); | Listener from NimbusLaF |
It means that the actual state of the button seeing from a user's mouseListener is unspecified and you shouldn't rely on any particular order.
Modal dialogs
Showing a modal dialog from a listener is the best way to observe your component in a transition state. If you run the following test and click an unselected list's item the modal dialog will come up and you'll be able to see the two selected items for a list with the SINGLE_SELECTION mode.
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.FlowLayout;
public class ModalityTest extends JFrame {
public ModalityTest() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
String[] data = {"One", "Two", "Three", "Four"};
JList list = new JList(data);
list.setSelectionMode(
ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
// Showing a modal dialog blocks the listener's notification
JOptionPane.showConfirmDialog(null,
"Do you see two selected lines ?");
}
});
add(list);
setSize(200, 300);
}
public static void main(String... args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ModalityTest().setVisible(true);
}
});
}
}

list.addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Showing a modal dialog with invokeLater
// doesn't leave the GUI in a transition state
JOptionPane.showConfirmDialog(null,
"Do you see two selected lines ?");
}
});
}
});
How should it have been implemented?
We seriously thought about improving the listeners notification several years ago but after heated disputes we didn't come up to one proposal. I offered to mark the Swing UI listeners with the existing UIResource interface and gurantee their notification before the user's listeners. The other propsal was to add a conception of listeners "weight" which will control the order in which a listener will be notified. So you would use the new add*Listener() methods with the second parameter - button.addMouseListener(listener, weight);
As a matter of fact the multiple listeners approach brings more problems than solutions. It would have been better to make the users override methods like processMouseEvent and automatically notify the same kind of method in the ComponentUI.
This was an entry from the Swing in a better world series
Thanks alexp
- Printer-friendly version
- alexfromsun's blog
- 2064 reads





