JavaSwingComponents: Opacity and background painting


Opacity and background painting

Opacity

Component defines a method isOpaque(). If this method returns true, the component guarantees it completely fills its area1, so that non of its ancestors (or siblings, if components are placed on top of each other) can possibly be visible "behind" it. Opaque components repaint more efficiently: only subcomponents have to be painted if such a component is asked to repaint.

JComponent turn this into a(n) (observable) property that can be set from the outside. This does not make sense in general: only the component itself knows whether it is "naturally" (or depending on other settings) opaque or not, and there is no general way to "become" opaque, short of hardcoding a Color that is known to be non-opaque (the background color may be transparent). The default value of this property is false, but subclasses and LAF delegates change this (most of the time with the intention of causing automatic background painting).

I wouldn't take setOpaque() to seriously; the component should do what it thinks it right (so often the suggestion of a value will be ignored). On the other hand, isOpaque() should always return the correct value, and a PropertyChangeEvent should still be sent the value changes (though there is no standard listener that is interested). While it isn't critical if it returns false although the component is opaque, the other way around repainting will not work properly (This happens with standard Swing components if you set a non-opaque background.) Probably changes of the status should also cause a repaint (but typically that will happen anyway due to the changes of the property(-ies) that caused the change.

Instead there should have been a separate property backgroundPainted that controlled automatic background painting (and, depend on the the background color and whether the "foreground" is already opaque, would update/determine the opaque property). This was recognized already in 2000 (#4334812), but still nothing has been done about it).

Automatic Background painting (for JComponents with UI delegate)

By default, the UI fills the complete area of the component with its background color if isOpaque() returns true. This ensures opacity only iff the background color has no alpha value. There are three kinds of problems:

Painting should be done, but the component is not opaque

This happens if you want to use non-opaque background colors. In (#4297006) this is noticed, but not considered an error.

For example, if you want a JLabel to paint its background color (which may happen to be partly transparent), you have to setOpaque() to true, but then isOpaque() does lie, and (re)painting will produce strange results. To fix this you can leave isOpaque() at false and do one of these:

Painting should not be done, but the component wants to be opaque

Background filling may be unnecessary because the paint method already fills every pixel. Especially with custom components, this may be the case; but these components may not, like specific UI classes, turn off the provided background painting because they have no way to change the UI directly (if they have an UI at all):

Then you can

(Partial) background painting is done although the component is not opaque

This is a consequence of the confusion between opacity and background painting and as such is not a bug (as it is sometimes considered to be, (#4789187), (#4685800), even (#4615385)).

First, only because setOpaque is most of the time interpreted as setBackgroundPainted do people except components to be "as transparent as possible" if opaque is false.

Second, even if background painting were turned off, there is nothing wrong with partial "background" (or what appears to be background) painting because even backgroundPainted would be low-level for automatic background painting. The requested kind of "becoming" transparent lives on a much higher level (such are also requested, see (#4261116))

Border opacity

Borders may be partly transparent (isBorderOpaque). But it never matters because either the component is opaque (even without the border), or it is not (no matter whether the border is). cf. (#4189266). Automatic background painting is done also behind the border.

Related notes on specific JComponents

AbstractButton: setOpaque() should not be used, instead setContentAreaFilled(). This is the proper way to suggest background painting from the outside (isOpaque() still breaks with transparent background colors).

JLayeredPane: has no UI delegate; thus paints background itself if isOpaque().

JDesktopPane: isOpaque() is overridden and always returns true, thus it always has its background filled. That means the implementation of setOpaque() is broken (fires events although nothing changed).

Renderers

Things are different for (non-exotic) renderer components: isOpaque() is never used except for determining whether the background should be filled.

The renderers can use setOpaque() on their JComponents (which they are typically themselves) to turn the automatic background painting on/off, independent from the fact that the background color may not be opaque at all..

DefaultTableCellRenderer even determines isOpaque (meaning isBackgroundPainted dynamically based on the background color of the table (in general this is broken)).

I think they should not turn background painting off just because the component they render for (JList/JTable/JTree) returns true from isOpaque (and they would paint in the component background color). That does not (in general) mean that their area is already filled by that color. First, the main component may return true only because it assumes its renderers fill the whole component. Second, isOpaque() may return false only because the component has discovered its background color has an alpha value, even though the background is already prepared, so it is misleading to assume isOpaque() returning false is a sign for the renderer to turn background painting on.


1 More specifically, assuming a standard Composite, paint(Graphics) must set every pixel value independent from the pixel values before painting.


(C) 2001-2009 Christian Kaufhold (swing@chka.de)