2009-01-24

Swing Tip: binding an action to a JList

I don't use JList much. It rarely makes it past the experimental version of a UI. A single-column JTable (which is one way of looking at JList) just don't crop up much. JList is easier to use, though, and doesn't look too bad out of the box — in stark contrast to JTable.

(JTable has been enough of a pain in my ass that sometimes I'll even go to the trouble of writing a fancy custom ListCellRenderer to avoid having to use a JTable. But those are stories for other days.)

But when I do use JList, I all too often make the mistake of thinking of it as a component to be double-clicked on, not used from the keyboard. You can see from the lack of any addActionListener method that its designers weren't too sure how to interact with it.

Handling double-click is easy, and it's documented in the class comment for JList. There's basically only one way to do it anyway, so I've never seen anyone get this wrong.

But what about handling enter? That's often neglected, and if you search the web you'll find all kinds of bad advice, from addKeyListener to subclassing JList!

What you really want to do is use the JList's ActionMap and InputMap. If you don't have an Action, just an ActionListener, you'll need to fabricate one, but AbstractAction makes that easy. You can just replace this:

new ActionListener() {
public void actionPerformed(ActionEvent e) {
// whatever...
}
};

With this instead:

new AbstractAction() {
public void actionPerformed(ActionEvent e) {
// whatever...
}
};

You'll need a name, too, because that's how you link the entry in the InputMap with the entry in the ActionMap. Something like:

final String name = "user-hit-enter";
list.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), name);
list.getActionMap().put(name, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
// whatever...
}
});

Of course, not being a filthy copy-and-paste merchant, you'll wrap that up in one of your utility classes somewhere. And personally, I'd rather deal with ActionListener in this instance, since the actionPerformed method is all I want from the Action, and an Action is-a ActionListener anyway, so the user can still supply a full-on Action if they happen to have one.

The "clever" part of my tip, though, is to basically have your own JList.addActionListener, and have it take an ActionListener but bind it both to double-click (via addMouseListener) and enter (via getInputMap/getActionMap). That way, you get into the habit of providing both by default.

As usual, there's code for this in my handy library of Java stuff linked to from the side of the page.