Over a million developers have joined DZone.

Custom Swing Component Development Tip: Not Everything is Transparent

· Java Zone

Discover how AppDynamics steps in to upgrade your performance game and prevent your enterprise from these top 10 Java performance problems, brought to you in partnership with AppDynamics.

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.

The Java Zone is brought to you in partnership with AppDynamics. AppDynamics helps you gain the fundamentals behind application performance, and implement best practices so you can proactively analyze and act on performance problems as they arise, and more specifically with your Java applications. Start a Free Trial.

Topics:

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}