Validation overlays using repaint manager

July 28th, 2007 | 4 Comments »

The RepaintManager class is one of the major participants in the Swing rendering pipeline and is, perhaps, one of the lesser known and used classes in the core Swing layer. In this entry i’m going to talk about this class, its strengths and weaknesses and hint at why it is so infrequently used in third-party libraries.

As i mentioned a couple of times in my presentation, one of the main disadvantages of using a custom repaint manager is that you can not have more than one repaint managers installed at any given time (singleton pattern). Coupled with asynchronous implementation of the core repaint manager, you can have two or more application modules (some potentially coming from third-party libraries that install their own managers “behind the scenes”) fighting over this global resource. This will very quickly lead to unpredictable painting behavior and severe visual artifacts. However, this is not the only reason why there are so few third-party custom repaint managers. First, i’ll mention three of these.

  • The RepaintManagerX from SwingX project is used to provide support for custom translucency on the JXPanel container (and all its children)
  • The ReflectionRepaintManager from the “Filthy Rich Clients” book samples is used to provide support for painting reflection of the panel’s contents.
  • The ExtensionRepaintManager provides a pluggable repaint manager that can have multiple custom implementations installed at the same time (this must find its way into the core JDK implementation some day).

The interesting thing about all three is that they all override only the addDirtyRegion and leave the paintDirtyRegions from the core implementation. In the first two examples above, the actual custom painting (setting custom alpha composite or painting the reflected contents) is done in the paint() method of the custom container itself. The reason is very simple – the paintDirtyRegions() of the core implementation is extremely inflexible.

The actual core implementation uses private fields that track the actual components to repaint, and the repainting of each such component is not exposed as an extension point. Furthermore, the implementation in JDK 6.0 is radically different from the implementation in JDK 5.0 (the former provides true double-buffer support on the frame level, eliminating the dreaded grey rectangle problem).

Let’s try and implement the validation overlays in a custom repaint manager without any changes to the paint() method of the actual components. As mentioned already, the core implementation doesn’t expose the list of all the elements that were marked as dirty. To address this, our first try in ValidationRepaintManagerFirstTry (all classes are in the org.pushingpixels.validation.repaintmanager package) would be to track all the dirty components in the addDirtyRegion() and paint the error icons after the call to the super (core) implementation in paintDirtyRegions(). Here is how the UI looks like:

Validation overlay, repaint manager first try

Let’s try and play with it a little. Clicking on the editable combobox reveals the first problem:

Validation overlay, repaint manager first try - problem

What happened? As i already mentioned in the groundworks entry, an editable combobox is actually a container that has a text field and a button. Swing is clever enough to understand that it needs to repaint only the textbox when it’s clicked, but not clever enough to see that the textbox is an integral (and internal) part of another component (our combobox). Thus, our first implementation actually never gets to paint the icon on the combobox, since it only sees the request to repaint the textbox. Note that this can be addressed by changing our application validation logic to handle the textbox-in-combobox explicitly. However, this introduces unnecessary coupling of the application logic with the internal look-and-feel specific implementation of an editable combobox (which may very across different LAFs).

Our second try will take this into account. The addDirtyRegion() of ValidationRepaintManagerSecondTry checks whether the component is a text field, and if it is, whether it lies inside a combobox or a spinner (note that here we assume that the current LAF uses text fields for editable comboboxes as well). If the conditions above are satisfied, we call the same method, but passing the parent container (combobox or spinner). The end result is that during the paintDirtyRegions() we will repaint the actual application component, and not one of its implementation children. Is this enough? It is for the first scenario (clicking inside a combobox). Let’s try something else – click on the button of the first combobox:

Validation overlay, repaint manager second try - problem

What happened here? The popup of the combo fits inside the frame bounds. In this case, Swing uses a lightweight popup implementation which just creates an additional layer on the frame root pane. However, our implementation of the paintDirtyRegions() first calls the super implementation (which paints all the components, including the popup layer), and then paints the error icons on all components that intersect with the dirty region. The end result is that the error icons are painted incorrectly on top of the popup.

Can this be addressed? It can, and ValidationRepaintManagerThirdTry paints the error icons after clipping out the areas of all the application popups (collected from the entire component hierarchy). The implementation details for such a simple functionality as validation overlays start to pile up rather unnecessarily. One of the main reasons is that we’re operating on a much higher level than we should be doing. While tweaking the dirty region for showing validation overlays is most certainly necessarily (and will, in fact, be used for a few other techniques), the actual painting process is very clumsy and inflexible. At this point, you can still end up with a few other visual artifacts playing with the lightweight / heavyweight popups and minimizing / restoring the frame. Here is one of them (note that the text fields don’t have error icons) – this happened after playing with the popups a few times:

Validation overlay, repaint manager third try - problem

One of the reasons for such behavior could be the optimizations done in the top-level double-buffering support, another could be in the specific implementation of the lightweight and heavyweight popup windows in the JDK itself and the look-and-feel you’re doing.

As you can see, there is a good reason why the available third-party repaint managers only handle the computations of the dirty regions, leaving the actual repainting to the component level itself. In the next sections, we’ll see how this can be achieved, and what are the potential problems along the way.


Related posts:

  1. Validation overlays using paint() In the previous entries on painting validation overlays using repaint manager and borders i discussed...
  2. Validation overlays using JXLayer To continue addressing the disadvantages of overriding paint() for showing validation overlays, this time i...
  3. Validation overlays using borders Every core Swing component inherits the setBorder() method from the JComponent. In this entry i’m...
  4. Validation overlays extending a specific look and feel With the last few entries essentially using the same part of the Swing painting pipeline...


4 Comments on “Validation overlays using repaint manager”

  1. 1 Romain Guy said at 9:44 am on July 29th, 2007:

    Note that Swing’s RepaintManager can also be implemented to return a different RepaintManager instance per component.

    Also, to better show the power and usefulness of the RepaintManager, maybe you should make the error icon overlap the component and its container, rather than be contained only within the bounds of the component.

  2. 2 Kirill Grouchnikov said at 12:39 pm on July 29th, 2007:

    Romain,

    How would that work? Say, you have two implementations that want to extend the dirty region, each one in its own way. Unless they know that they should behave nicely and not uninstall each other (say, something like the ExtensionRepaintManager that i mentioned from Matt’s incubator), i don’t see that happening. Do you have such a sample implementation in mind?

    And your second remark is correct (not only for repaint manager, but also for glass pane and layered pane), but i just don’t think that repaint manager on its own is right for this job. Too little is exposed in the core API, and too much is going on inside the core implementation to properly handle the overlays in a simple and intuitive way. IMHO.

    Thanks
    Kirill

  3. 3 Alexander Potochkin said at 2:25 pm on July 31st, 2007:

    Hello Kirill

    I just found your new site, well done !
    Can I add CheckThreadViolationRepaintManager
    to your list of custom RM implementations ?

    Thanks
    alexp

  4. 4 Kirill Grouchnikov said at 11:30 pm on July 31st, 2007:

    Hi, Alex

    Thanks for adding a link to that repaint manager. The main reason i left it out is because it is not used for the purpose of repainting itself. But it is a very interesting usage of repaint manager :)

    Kirill