Friday, September 24, 2004

Checkboxes in a JTree.

I've been having fun over the last couple of weeks getting to tidy up some of the code that I created a long time ago. For the administration client application I had gone through several designs for a standard tree view of resources, based on the Java JTree class.

By the end of the project I had about five different versions, all sharing different bits of code, for example some had their own specific TreeNode implementations, most shared a TreeCellRenderer etc.

I had to go in and fix a bug in these trees and thought I would take the opportunity to rationalise all this work into one standard tree component and an extention of that which turned the tree into a form, with checkboxes or radio buttons either at the leaf nodes or on all the group nodes.

So I created a new TreeCellRenderer which created a TreeCell (one of my classes that extends JPanel) for each cell. In the TreeCell were the usual things, but also either a checkbox or a radio button. This all worked fine in a tree where you can only select the leaf nodes, however in a tree where the leaf nodes are not display and group nodes can be selected I ran into trouble.

You see, I could not use a mouse listener on the checkbox, or infact listen for events from the checkbox. I am guessing that the JTree has some glass pane over it thus preventing this from working. So I extended MouseAdapter, put that on the tree and got the tree row from the mouse click co-ordinates, thus giving me the TreeNode which I could now inform to de/select its checkbox.

All seemed well, except in some strange cases where clicking on the plus symbol to open a part of the tree would pass the mouse event down to the TreeNode. This would only happen when the group node in question didn't actually have any sub-groups underneath it, thus the plus symbol dissappeared.

It took me a long time to find the solution to this, I tried screen co-ordinate translation (note don't ever try to get this working with tree cells!). I added a tree selection listener, but of course the selection this is talking about is nothing to do with my checkboxes. I could select a node, but then not unselected it, because the underlying node was still seen as selected and so the event would not get passed to the listener.

In the end I had to create a class which implemented TreeSelectionListener and extended TreeSelectionModel. This picks up the tree selection ("valueChanged") event, finds the node from the selected TreePath and tells it to toggle its checkbox/radio button. Then I call this.clearSelection() to the super TreeSelectionModel. Unfortunately this then triggers another "valueChanged" event, so I had to add a re-entry boolean to stop this from happening.


public class TreeMouseListener
extends DefaultTreeSelectionModel
implements TreeSelectionListener, TreeSelectionModel {

private boolean m_bReEntryStop = false;

public void valueChanged(TreeSelectionEvent tse) {
if(!m_bReEntryStop) {
TreeNode node =
(TreeNode)tse.getPath().getLastPathComponent();
TreeCell cell = this.m_tree.getCellForNode(node);
if(cell.isEnabled()) {
cell.mouseClicked(null);
m_bReEntryStop = true;
this.clearSelection();
m_bReEntryStop = false;
}
}
}
}

2 comments:

Anonymous said...

Your blog is really hard to read. Small fonts, bad contrast,too dark background.

Matt Large said...

I always kinda liked that template, but you are right. Never really thought about it. Changed to the lighter version of that template. Hope people like better.