I recently wanted to experiment with creating my own
ComboBoxUI. Using the
FilledButtonUI model as a starting point: I created the
FilledComboBoxUI. (See the demo here.) It wasn't as straight-forward as I'd expected it to be: but the current draft weighs in at only 500 lines of code.
Unfortunately this highlighted a bug regarding applets in many browsers:
JPopupMenus would be positioned relative to the top-left corner of the display, and not the applet. The result is that you would click a combobox and the popup menu would appear several pixels above and to the left. The same problem was also observed for tooltips.
To the right is a screenshot of this bug in the Wordle applet: although I clicked the "Font" menu, the popup that displayed is several pixels away from where it should be.
After a couple of hours of experimentation: I gave up trying to reposition the heavyweight popup.
Instead: as a work-around I decided to try displaying the popup as a lightweight component. It's not very common that we need to use the powerful complexity of the
JLayeredPane: but it has the potential to display
JComponent above everything (it even has a layer specifically designated for popups).
Originally my goal was only to implement a lightweight model for my new
FilledComboBoxUI class (because I had such explicit control over how the popup would be invoked), but as I rummaged around I realized the
javax.swing.PopupFactory is an existing architecture that may let me intercept all popups (including tooltips).
The final result is the new
AppletPopupFactory (source here). After you invoke the static
initialize() method: it will handle all popups. If a popup is requested outside of an applet: then the original
PopupFactory is used instead (so if it's invoked in an application vs an applet: then the fancy new code isn't touched).
Additional problems included:
PopupFactoryI tried calling
JPopupMenu.setVisible(..)to help control visibility, because it is still a
JComponent. Unfortunately: this is a complex invocation that ultimately defers to the current
PopupFactory. The result (if I try to use
setVisible(..)) is a recursive loop that never actually alters visibility. The solution was to simply avoid interacting with the visibility. (The
JPopupMenuis a strange creature, and now I know it can function as a normal lightweight
JComponentas long as you don't touch its visibility.) Instead: adding it and removing it from the parent
JLayeredPanecan achieve the necessary effect.
MouseEvents. This means we notice when the user clicks in this area (so we can hide the popup), and we prevent the user from clicking any other component while the popup is visible.
MouseEvents: I tweaked this so it rendered a small shadow around the popup. The border proved a stranger problem: when the
AquaComboBoxUIwas being used on Mac 10.7.5 (Java 1.7): the border was never rendered. It was correctly defined as a simple
LineBorder-- and the same
LineBorderrendered correctly with the
FilledComboBoxUI-- but with the Aqua model it never appeared. The (strange) work-around here was to remove the border and render it along with the shadow in the background pane. This is an unusual separation of a component from its border: but it should be visually indistinguishable for the user.
I expect to further tweak the
FilledComboBoxUI in coming weeks as I try to further improve it, but overall everything is shaping up well. There is no applet accompanying this article, but you can see these changes in action in this applet by interacting with the tooltips and comboboxes.
This article focuses specifically on the
PopupFactory and creating a lightweight alternative: and that effort appears to be finished.
As a sidenote: you could also argue, "Who cares about applets?" The primary bug this article addresses only occurred in applets, but not in applications: so why fuss about applets? I attended a talk last week led by Roger Brinkley titled "Java Platform Now and the Future", and it seems safe to say the future of Java UI development is (at best) concentrated towards JavaFX. But this (my java.net repository) is not a business -- it's a hobby. And for the time being I'm not giving up on Swing quite yet. And in the mean time: this bug affected several of my articles/applets. So for those users brave enough to click through the security warnings and actually run my applets: I wanted to at least give them a decent experience.