August 3rd, 2007

Glass panes and lightweight popup menus

Matt Nathan has asked an excellent question in the comments section of the “Validation overlays using glass pane” entry – what happens when you have your validation field under a lightweight menu?Let’s see what happens when we click on the button of the first combobox:

Validation using glass pane - the problem

As you can see, the validation overlays painted by the glass pane are actually painted on top of the combobox popup! This is, of course, not the desired behavior, and here is a short explanation of what is going on.

As explained in Swing tutorial on root panes, a root pane consists of a sequence of “layers”. The layered pane (which i will talk about in the next entry) has the content pane and the menu bar (the bounds of these two are computed by the root pane UI delegate, which will be important for the next entry). The glass pane is “located” on top of the layered pane:

Root pane layers

The layered pane itself is a sequence of layers, as illustrated in the diagram below:

Layered pane layers

So, where does the combobox popup “live”? It depends on whether it is a lightweight popup or a heavyweight popup. In general, a popup that is completely contained inside the frame bounds is called lightweight and lives in the popup layer of the layered pane. A popup that extends beyond the frame bounds is called heavyweight (and is implemented as a separate Window). And here you can see the reason for the incorrect behavior of our glass pane – it is painted on top of the layered pane, and hence on top of the lightweight popup layer.

One possible way to address this issue is suggested by Matt himself (and is very similar to what i did in the repaint manager entry) – go over all visible lightweight popups and adjust the clipping mask during the repaint of the glass pane. This can get especially tedious when the validation overlays are offset (as in the original glass pane implementation) and you can very quickly get lost in the implementation details.

Another and much simpler way would be to disable the lightweight popup menus by calling JPopupMenu.setDefaultLightWeightPopupEnabled(false) before showing your frame:

Validation using glass pane - the solution

As you can see, the validation overlays are correctly painted since the heavyweight popup window (combobox popup) is painted after the frame is painted.

Note that in our particular case, the desired behavior is that the glass pane painting is done before the popups that hide the relevant controls. This is not always the case, and you might want to have glass pane painted after the popups. In such a case, you will have to provide special treatment for heavyweight popups since they live in their own top-level windows.

An additional implementation of a glass pane that respects the lightweight popup menus for our scenario might do the following after painting the validation overlays (sample implementation in org.pushingpixels.validation.glasspane.ValidationGlassPaneForPopups):

  • Starting from the root pane
  • Go over all components recursively
  • If a component is a JPopupMenu, is showing and is visible
  • Paint it on the Graphics passed to the paintComponent() of the glass pane

The end result should be identical to the last screenshot above. As already mentioned, this might not be the desired functionality for all glass pane usages. For example, if your glass pane shows a translucent fill with infinite progress indication, you might not want the popups to be painted with full opacity.