Lessons Learned #1: JComboBox is actually quite complex...
I talked a little while ago about taking an opportunity to rebuild our ResourceTree component, which is a reusable tree view over resources on our server. I talked about extending this into a FormResoruceTree which added checkboxes or radio buttons to each node. One place that the plain ResourceTree is used is in a JComboTree component. This is a JComboBox like component that contains a ResourceTree in the drop down instead of a list of options.
I first built this component months ago. We had a requirement to be able to select a single resource in forms, but we had limited space to layout in. A temporarily display ResourceTree seemed to be the way to go and my project manager being the guy he is(*) he "suggested" it should work like the JComboBox.
It took me almost a week to get this working. I had thought it would be quite quick. Turns out JComboBox does some interesting things to be able to display the drop down over all the other components, even over the edge of the main window.
What I produced in the end was a JPanel containing a JLabel and a JButton. The JLabel holds the selected value and the JButton opens the drop down with the ResourceTree in it. The drop down of course is no such thing, it's a floating borderless JWindow, which is aligned with the bottom of JLabel and JButton to look like a drop down.
The JWindow is hidden when a value is selected from the tree, or if the JButton is pressed again. The problem with this has been that if you don't close the JWindow, by selecting a value or pressing the button, the it will remain floating in space.
Obviously this has been reported as a bug...many times.
After creating the FormResourceTree that I talked about before, my project manager suggested that I use this in the JComboTree, especially where we allow multiple selections within it. This didn't take too long to do, and removed another of the many JTree implementations used throughout the code. He also suggested that I fix that "annoying" floating window problem, so that it disappears when you click away from it, just like a proper JComboTree.
So how can you do this with any certainty? If you are anything like me the first two things you will think about are that the solution is either something to do with FocusListeners or MouseListeners.
Well I'll tell you now that it's nothing to do with MouseListeners. What would you put them on? Everything else in the programme? No, nothing to do with mouse listeners.
The FocusListener is a good start. Listen for FocusLost events on your JWindow. This works to a degree, as focus in Swing is defined as following keyboard entry focus. So this will work for the cases where the user moves onto another component with some keyboard entry features. However if your JComboTree is in a tabbed pane and the user switches to another tab, this apparently doesn't count.
So where do you look for a solution. Well my next step was to realise that JComboBox works, and therefore I should check out how this does it. A quick check in the JavaSDK source later and I had the solution. AncestorListener. I'll admit I'd never heard of this before. According to the JavaSDK JavaDocs it is this;
"AncestorListener Interface to support notification when changes occur to a JComponent or one of its ancestors. These include movement and when the component becomes visible or invisible, either by the setVisible() method or by being added or removed from the component hierarchy."
This is nearly perfect. After implementing this our pesky JWindow disappears in almost all cases, at least I can be sure that it will go eventually. People seem a lot happier now in any case, and that will do me.
So what are the "Man at arms" style morals from this tale. Firstly Swing components can often be far more complicated than you realise until you try to replicate their functionality. Secondly, keep the JavaSDK source close to hand, it can often provide inspiration, or at least explain what's going on under the hood.
* Just to explain, my project manager on this project has very strong views on how interfaces should work. Often this is far from the accepted norm, and therefore what is directly supported in frameworks like Swing, but almost always the right thing to do. Hence why I've spend most of this project extending or replacing Swing components.