Mixing Heavyweight and Lightweight Components
By Sharon Zakhour and Anthony Petrov
Published April 2010
Historically, in the Java language, mixing heavyweight and lightweight components in the same container has been problematic. However, as of the JDK 6 Update 12 and JDK 7 build 19 releases, mixing heavyweight and lightweight components is easy to accomplish. This article explains some details you might need to know.
A Bit of History
There are two kinds of graphics components in the Java programming language: heavyweight and lightweight. A heavyweight component is associated with its own native screen resource (commonly known as a peer). Components from the java.awt package, such as Button and Label, are heavyweight components. Third-party heavyweight components, such as the following, are also becoming increasingly popular:
A lightweight component has no native screen resource of its own, so it is "lighter." A lightweight component relies on the screen resource from an ancestor in the containment hierarchy, possibly the underlying Frame object. Components from the javax.swing package, such as JButton and JLabel, are lightweight components.
In the past, mixing heavyweight and lightweight components in the same graphical user interface (GUI) caused problems when those components overlapped one another. For example, the following screen capture shows a menu bar that contains both a lightweight menu and a heavyweight menu. The frame contains a heavyweight button. The heavyweight menu behaves as expected: When selected, the heavyweight menu is displayed on top of the button. But, when the lightweight menu is displayed, the heavyweight button takes precedence where they overlap.
In this next screen capture, the lightweight button behaves properly, but the heavyweight button overlaps the lightweight scrollbar.
In this final screen capture, there are two overlapping JInternalFrame instances, one frame containing a heavyweight button and the other frame containing a lightweight button. Though Internal Frame 2 overlaps Internal Frame 1, the heavweight button in Frame 1 appears on top.
Component Mixing Just Works
As of the JDK 6 Update 12 and JDK 7 build 19 releases, it is now possible to seamlessly mix heavyweight and lightweight components within the same container. The following screen captures show the same three examples, run under the JDK 7 release, with no changes to the code. In each case, it just works.
Requirements
The capability to mix heavyweight and lightweight components works by default, if you are using the JDK 6 Update 12 release or the JDK 7 build 19 release. In most cases, this functionality will just work, but there are a few situations that might affect your code:
- The component hierarchy must be validated whenever it becomes invalid. If any part of the hierarchy is invalid, the mixing will not operate correctly. Due to a current bug, invoking the Swing revalidate method might not be enough. As a workaround, invoke validate on the top-level component (usually a Frame) instead of (or just after) invoking revalidate.
The following workaround, pulled from the Calc example, shows how you might do this:
private void updateScreen() { jTextField1.setText("" + screenR); // NOTE: Here we need a workaround for the HW/LW Mixing feature to work // correctly due to yet unfixed bug 6852592. // The JTextField is a validate root, so the revalidate() method called // during the setText() operation does not validate the parent of the // component. Therefore, we need to force validating its parent here. Container parent = jTextField1.getParent(); if (parent instanceof JComponent) { ((JComponent)parent).revalidate(); } // ... and just in case, call validate() on the top-level window as well Window window = SwingUtilities.getWindowAncestor(jTextField1); if (window != null) { window.validate(); } }
- If you are adding heavyweight components to a JScrollPane, the scroll pane must use the SIMPLE_SCROLL_MODE mode. The BLIT_SCROLL_MODE and BACKINGSTORE_SCROLL_MODE scrolling modes are not supported because they have been optimizied to paint lightweight components. You can change the scrolling mode by using the JViewPort.setScrollMode(int) method.
- Mixing a non-rectangular lightweight component with a heavyweight component requires special handling. Typically, creating a non-rectangular lightweight component, such as a button with rounded edges, is achieved by making the component non-opaque and redefining its contains method to paint the component as desired. However, when this rounded button is mixed with a heavyweight component, it is treated as a traditional rectangular component, resulting in an undesirable visual effect at the corners. To work around this problem, you can use the non-public API method setComponentMixingCutoutShape in the com.sun.awt.AWTUtilities class. Here is the API specification for this method, from bug 6797587:
/**
* Sets a 'mixing-cutout' shape for the given component.
*
* By default a lightweight component is treated as an opaque rectangle for
* the purposes of the Heavyweight/Lightweight Components Mixing feature.
* This method enables developers to set an arbitrary shape to be cut out
* from heavyweight components positioned underneath the lightweight
* component in the z-order.
*
* The {@code shape} argument may have the following values:
*
* - {@code null} - reverts the default cutout shape (the rectangle equal
* to the component's {@code getBounds()})
* - empty-shape - does not cut out anything from heavyweight
* components. This makes the given lightweight component effectively
* transparent. Note that descendants of the lightweight component still
* affect the shapes of heavyweight components. An example of an
* empty-shape is {@code new Rectangle()}.
* - non-empty-shape - the given shape will be cut out from
* heavyweight components.
*
* The most common example when the 'mixing-cutout' shape is needed is a
* glass pane component. The {@link JRootPane#setGlassPane()} method
* automatically sets the empty-shape as the 'mixing-cutout' shape
* for the given glass pane component. If a developer needs some other
* 'mixing-cutout' shape for the glass pane (which is rare), this must be
* changed manually after installing the glass pane to the root pane.
*
* Note that the 'mixing-cutout' shape neither affects painting, nor the
* mouse events handling for the given component. It is used exclusively
* for the purposes of the Heavyweight/Lightweight Components Mixing
* feature.
*
* @param component the component that needs non-default
* 'mixing-cutout' shape
* @param shape the new 'mixing-cutout' shape
* @throws NullPointerException if the component argument is {@code null}
*/
public static void setComponentMixingCutoutShape(Component component,
Shape shape);
Note:The com.sun.awt API is not part of an officially supported API and appears as an implementation detail. The API is only meant for limited use outside of the core platform. It might change drastically between update releases, and it might even be removed or moved to another package or class. Therefore, this method should be used only through the Java reflection mechanism. For example:
try {
Class awtUtilitiesClass = Class.forName("com.sun.awt.AWTUtilities");
Method mSetComponentMixing = awtUtilitiesClass.getMethod("setComponentMixingCutoutShape",
Component.class, Shape.class);
mSetComponentMixing.invoke(null, component, shape);
} catch (NoSuchMethodException ex) {
ex.printStackTrace();
} catch (SecurityException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
}
Limitations
A few situations are not supported:
- Non-opaque lightweight components that have translucent pixels (0 < alpha < 255) are not supported. If a partially translucent lightweight component overlaps a heavyweight component, the heavyweight component will not show through.
- Embedded heavyweight components must belong to the process that created the frame or applet. The heavyweight component must have a valid peer within the main process of the application (or applet).
- Advanced Swing key events, such as those events maintained in an InputMap, might not work correctly where lightweight and heavyweight components are being mixed. There are no known workarounds.
Some applications have already implemented a mechanism to mix lightweight and heavyweight components. In this situation, this built-in mixing feature might cause problems. The mixing feature can be turned off by using the private sun.awt.disableMixing system property. For more information, see bugs 6788954 and 6851271. By default, the mixing feature is enabled.
Demo
Here is a basic NetBeans calculator example you might find useful, Calc.zip. In this example, the buttons for the numbers and mathematical symbols are heavyweight, and the menu is lightweight. If you have the correct version of the JDK installed, they will interact correctly.
Otherwise, if you are using an older version of the Java platform, the buttons will overlap the pop-up menus.
Click here to run the calculator example.
Sharon Zakhour is a staff technical writer at Oracle. Her responsibilities include the Java Tutorials and the tutorials portal. She enjoys travel and plans to do more after her two teenaged daughters flee the nest.
Anthony Petrov is a software engineer in the AWT team. His areas of responsibility include top-level windows, splash screen, and other.