Introduction
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.
Solution
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:
PopupFactory
I 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 JPopupMenu
is a strange creature, and now I know it can function as a normal lightweight JComponent
as long as you don't touch its visibility.) Instead: adding it and removing it from the parent JLayeredPane
can 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 AquaComboBoxUI
was 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 LineBorder
rendered 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. Conclusion
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.
No comments:
Post a Comment