I am the lead developer at 'Java Swing Compoments' and am extremely passionate about developing UIs. To this day I still blame Logo the turtle for introducing me to programming... I have never looked back! Rhiannon has posted 6 posts at DZone. View Full User Profile

Custom Swing Component Development Tip: Not Everything is Transparent

11.04.2010
| 7793 views |
  • submit to reddit

This article explains transparency (opacity) and how to avoid some common pitfalls encountered when developing custom swing comppnents The previous article in this series, titled Insets Matter, introduced us to a new Swing developer named Toni and documented his efforts to create a custom swing component named the Orb. The purpose of the Orb is to draw a simple blue circle within the bounds of the component.

Toni's current code and output can be seem below.

private class Orb extends JComponent {
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);

//create a new graphics2D instance
Graphics2D g2 = (Graphics2D) graphics.create();

//determine the actual x, y, width and height
int x = getInsets().left;
int y = getInsets().top;
int w = getWidth() - getInsets().left - getInsets().right;
int h = getHeight() - getInsets().top - getInsets().bottom;

g2.setPaint(Color.BLUE);
g2.fillOval(x, y, w, h);
}
}

Everything has been going really well for Toni, everyone in the company was impressed by the recent demo of his Orb. After a few more days of flawless testing the Orb was distributed to the company's web site, whereby the orders for the Orb flooded in.

After a few weeks, customers who purchased the Orb started complaining of two serious problems. Firstly they are unable to change the background color of the Orb, and secondly after they attempted to change the background color, strange rendering artifacts started appearing.

Upon reading the bug reports Toni immediately realises that his code does not take into account the component's opacity and background color. Toni's understanding of a components opacity is that it is the opposite of transparency. When a component has opaque = false, it will be transparent, and not paint its background. However, when a component has opaque = true, it will not be transparent and will then use its background color to paint the background of the component.

In order to reproduce the defect Toni writes some code to create a new instance of the Orb, sets its opacity to true and set its background color to red.

Orb orb = new Orb();				
orb.setBackground(Color.RED);
orb.setOpaque(true);

Upon running the test Toni can clearly see that the Orb does not cater for a component's opacity. Toni now adjust his code to the following.

private class Orb extends JComponent {
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);

//create a new graphics2D instance
Graphics2D g2 = (Graphics2D) graphics.create();

//determine the actual x, y, width and height
int x = getInsets().left;
int y = getInsets().top;
int w = getWidth() - getInsets().left - getInsets().right;
int h = getHeight() - getInsets().top - getInsets().bottom;

//this will fill the component's background
//based on the component's opacity and
//the component's border and insets
if (isOpaque()) {
g2.setPaint(getBackground());
g2.fillRect(x, y, w, h);
}

g2.setPaint(Color.BLUE);
g2.fillOval(x, y, w, h);
}
}

Toni again runs his test program and is glad to see that the Orb's background is turning red in his test. As for the rendering artifacts, Toni's quick tests were unable to reproduce them in any form and he quickly (and naively) put them down to some quirk on the customer's machine.

Everyone is pleased with Toni's changes and the an update is subsequently sent down to all the customers.

A few days pass, and Toni receives some disturbing feedback, although the customers are now happy that the Orb caters for its background color, however the original customers are still reporting visual artifacts around the component's border. To make matters worse, the rendering artifacts are no longer confined to the original customers but are now occurring at nearly all of the company's customers. Toni is now starting to panic as he is still not able to reproduce the artifacts in a single test.

Lets leave Toni for a moment and discuss the problem that he is now facing. The problem actually lies with Toni's understanding of a component's opacity. Toni's understanding of a component's opacity is about 85% correct. Yes, a component's opacity does determine if a component is transparent and whether or not it should have its background filled with its background color. However this is slightly more to it.

A component's opacity is also a contract with the underlying swing framework. Opacity more correctly relates to whether or not the component will paint all the pixels within the confines (bounds) of the component.

When a component's opacity is set to false, swing paint the component in such a way that it appears to be transparent. It does so by first painting the parent container and then painting the component over the parent, so as to make the component appear to be transparent.

When a component's opacity is set to true, the component has declared that it is not transparent, and that it will fill all of the pixels in the component's bounds. Swing expects the component to abide by this contract and will therefore not paint the components that are currently behind the opaque component.

If one violates this contract and does not draw over all of the component's pixels, rendering artifacts appear, most commonly in the form of other components appearing in the background of your component. To make matters worse these artifacts are not common on simple screens and only manifest after a variable amount of time. A simple test will in most cases not be able to reproduce them.

Revisiting Toni's code, we can see that by taking the component's border into account by using insets, he is leaving the border area of the component unpainted. This is what is causing Toni's rendering artifacts. One may argue that it should be the responsibility of the border to paint those pixels, however this can not be guaranteed, as the border may be an EmptyBorder or a border that is not fully opaque.

In order to correctly handle a component's opacity, the background area filled should ignore the component's insets and fill the entire bounds of the component.

After stressing himself to the point that he is convinced the Orb has taken at least 2 years off his life, Toni discovers an article detailing the contract between swing and a component's opacity. He immediately updates his code to the following:

private class Orb extends JComponent {
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);

//create a new graphics2D instance
Graphics2D g2 = (Graphics2D) graphics.create();

//determine the actual x, y, width and height
int x = getInsets().left;
int y = getInsets().top;
int w = getWidth() - getInsets().left - getInsets().right;
int h = getHeight() - getInsets().top - getInsets().bottom;

//this will fill the component's background
//based on the component's opacity and
//fill the entire bounds of the component.
if (isOpaque()) {
g2.setPaint(getBackground());
g2.fillRect(0, 0, getWidth(), getHeight());
}

g2.setPaint(Color.BLUE);
g2.fillOval(x, y, w, h);
}
}

After changing his code, Toni runs his test again.

Although the output of the test is not exactly visually appealing, it displays the correct behaviour, and abides by the rules laid down by the swing framework.

The moral of the story, if your component is opaque, it must fill all of its pixels. Failing to do so will cause your developers to grow grey hairs while they spend hours trying to reproduce a real problem, that simply never seems to show up in simple test cases.

Additional swing related articles can be found at the Custom Swing Components site under the blogs section.

Published at DZone with permission of its author, Rhiannon Liebowitz.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Robert Saulnier replied on Thu, 2010/11/04 - 8:06am

Great article! Now I'm at 99%.

In your code, you're creating a new Graphics2D object. Should it be dispose()'d at the end of the paintComponent(...) method?

From the Javadocs Graphics.dispose():

For efficiency, programmers should call dispose when finished using a Graphics object only if it was created directly from a component or another Graphics object.

Rhiannon Liebowitz replied on Thu, 2010/11/04 - 1:11pm in response to: Robert Saulnier

Hi Robert

You are totally correct that Toni's code should call dispose on a graphics object when he is done with it I am really impressed that you noticed that the dispose was missing! The ommission of the dispose on my side was intentional and I planned on making the correction in the next article. 

The goal of these series of articles is to start with a very simple custom component and evolve it into a final component that caters for as many common pitfalls as possible. I use Toni as an example of a developer that always takes the single most direct approach to solving a problem... However in taking such a direct approach, he makes the sort of mistakes, that most new swing developers make.

I try write the article in such a way that the solution to Toni's problem is the tip I'm trying to explain in the article.

The next article will touch on accidentally overwriting settings on the graphics object, that occur if you cast the graphics object instead of calling create, and will discuss these two options.

The overall goal of the series of the article is to develop a nice checklist of things that should be done to ensure your new custom component behaves correctly. So far we have covered insets and opacity... and there are plenty more things to cover.

The problem with evolving the code is that until the final Orb is written, there will always be deficiencies with the component. The current component ignores if it is enabled, does not override the contain method to match the shape of the Orb. There are no checks to see if the Orb is even in the current clip. As time goes by I hope to cover as many of these things as possible.

Regards

Rhi

Pavan Kumar Sri... replied on Fri, 2010/11/05 - 12:52pm

Hi Rhiannon,

Nice article ..very nicely explained with a use case! 

Just wanted to add that ..the 'opaque' property is one of the primary diff between extending JComponent & JPanel(opaque by default)  . If Orb were to extend JPanel super.paintComponent would have filled the component width & height with the background color  ..if opqaue property was set.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.