Validation overlays extending a specific look and feel
With the last few entries essentially using the same part of the Swing painting pipeline (overriding or extending paint() in one way or another), it’s time to continue along the pipeline. In this entry i’m going to talk about the using the UI delegates to show validation overlays and the various issues with this approach.
In general, almost all parts of core Swing components are painted by the UI delegates. A UI delegate is a class that is responsible for painting a specific class of Swing components. For example, JButton
s are painted by the ButtonUI
delegate, JSlider
s are painted by the SliderUI
delegate and so on. Together, UI delegates form what is called a look and feel. There are a few core LAFs (such as Metal / Ocean, Windows, GTK, Motif, Synth and the upcoming Nimbus) and about 15-20 third-party commercial and open-source ones (a comprehensive list is available at javootoo.com). While in general almost everything is painted by the UI delegates, this does not automatically mean that they are the best answer to all problems, and i’m going to address different LAF-related issues in this entry.
The classes in this entry are located in the org.pushingpixels.validation.specificlaf
package and implement the validation overlays using a custom look-and-feel that extends the Substance LAF. In general, you would have to extend a UI delegate for each one of the component classes that can have validation overlays.
Our first attempt is straightforward -all the UI delegates inherit the update(Graphics g, Component c)
method from the common base ComponentUI
class. This is the method that is called in JComponent.paintComponent()
to paint the actual component. In our ValidationComboBoxUIFirstTry
and ValidationTextFieldUIFirstTry
we override the update method, call the super implementation and then paint the validation icon. Here is the screenshot of our UI:
Obviously, this is not what we want. What is happening here? For starters, look at the second part of the Swing painting pipeline – after paintComponent()
is done, Swing calls paintBorder()
. This effectively means that the component border will be painted after the UI delegate is done painting the component. See what happens with the top (non-editable) combobox and the first text field – the validation error is partially overdrawn by the component border. How can we address this problem?
Unfortunately, the solution is not on the pure look-and-feel level. We have to install a custom border in much the same way as discussed in the section on borders. The advantage of doing this on the LAF level is that the application UI creation logic remains the same – the borders are installed by the UI delegates of the relevant components. As mentioned in the section on borders, the current core implementation of the Swing pipeline allows the paintBorder()
method to paint in the entire bounds of the component (doesn’t install a custom clip that matches the border insets). So, let’s see how our application looks like under the ValidationComboBoxUI
and ValidationTextFieldUISecondTry
:
The first combobox and the first text field look as expected. What about the second (editable) combobox? As already mentioned, an editable combobox is implemented with a text field and a button. These are painted in the paintChildren()
method, which is called after paintComponent()
and paintBorder()
are done. And so, the inner text field of the editable combobox partially hides the validation icon painting of the “container” combobox. How can this be addressed?
The ValidationTextFieldUI
overrides the update()
method in addition to installing a custom border. While the border is painting the validation overlay on regular text fields, the update()
method traverses the component hierarchy, checks whether the current parent needs to show a validation overlay and if so, paints the validation icon offset to the parent coordinates. Here is how the application looks now:
And now that we have seen how the validation overlays can be achieved by extending a look-and-feel, i’ll talk about the good and the bad sides of doing so.
By doing the custom painting on the LAF level you’re decoupling this functionality from your main application. In addition, you would be able to reuse this LAF in another application. On the other hand, there are many shortcomings to this approach:
- By extending a specific look-and-feel, you’re binding your application to it. While this can be addressed with bytecode modification (injecting the required functionality into any core or third-party LAF either offline or in the classloader), doing so is very complicated, might not work on LAFs that don’t follow the usual painting cycle (such as WinLAF or Synth) or not feasible (such as changing core LAFs in the classloader).
- Much as with repaint manager or glass pane, you can only have one active look-and-feel at any given time. If you need to implement additional functionality using this approach, the code becomes much less reusable and maintainable. The multiplex look-and-feel that i will talk about in the next entry brings more problems than it solves.
- The LAF operates further along the way of the Swing painting pipeline than what is needed for this specific functionality. This makes the implementation more complicated by explicitly handling the borders and internal components. In case of borders, this might interfere with existing application logic when property change listeners will be activated on changing the border.
- As with other component-level techniques, we still need to install a custom repaint manager that expands the dirty region from an inner component to the actual component that shows the validation overlay. This can be addressed by firing a repaint event whenever such an inner component is repainted (to repaint the parent component). The implementation needs to make sure that there is no infinite repaint loop. In addition, since the repainting in Swing is asynchronous, you might end up with temporary visual artifacts.
- You need to provide a UI delegate for every component class that can have validation errors. As seen in the examples above, some of these UI delegates will have to provide a complex logic for painting the validation overlays. In addition, some of them will have to take into account rather “extreme” cases such as using unusual components in the combobox editor (that can take any component). So, the code that we have seen in the last version of the text field UI delegate will have to be replicated for a lot of other controls (especially if you’re planning to ship such a LAF as a standalone library that is not specific to one of your applications where you have complete control).
The next entry will talk about the multiplex look and feel that is trying to address some of the problems mentioned above, but brings even more problems with it.