13 Swing

This chapter provides information and guidance on some specific procedures for troubleshooting some of the most common issues that might be found in the Java SE Swing API.

This chapter contains the following sections:

General Debug Tips for Swing

Swing's painting infrastructure changed quite extensively in Java SE 6. If you notice painting artifacts specific in Java SE 6 or later releases, you can try turning off the new functionality. This can be done with the property swing.bufferPerWindow.

When you are debugging the Swing code which is executed while any menu is popped up, it is recommended to use the debugger remotely. Otherwise, the debugging process and the application execution block each other, and this prevents further work with the system. If that happens, the only action that can be taken is to kill the X server for Oracle Solaris and Linux. See Bug Database.

The following are some common Swing problems:

  • Painting.

  • Renderers.

  • Updating models from wrong thread.

  • Hangs.

  • Responsiveness.

  • Repainting issues.

  • isOpaque usage.

  • Startup: could be caused by small heap, loading unnecessary classes.

The following are some things to consider:

  • Buffer-per-window feature.

  • Native look-and-feel fidelity: Gnome vs Windows

  • Footprint of Swing applications.

  • JTable, JTree, and JList all use renderers.

  • Make sure that custom renderers do as little as possible.

  • Update models only from event dispatch thread. Otherwise the display will not reflect the state of the model.

The following identify bad renderers:

  • Sluggish application, especially when scrolling.

  • Use an optimizer to watch painting calls, look for calls to getTableCellTRendererComponent.

Specific Debug Tips for Swing

The following topics describe problems in Swing and troubleshooting techniques:

Incorrect Threading

Random exceptions and painting problems are usually the result of incorrect threading usage by Swing.

All access to Swing components, unless specifically noted in the javadoc, must be done on the event dispatch thread. This includes any models (TableModel, ListModel, and others) that are attached to Swing components.

The best way to check for bad usage of Swing is by using instrumented RepaintManager, as illustrated in the following example.

public class CheckThreadViolationRepaintManager extends RepaintManager {
     // it is recommended to pass the complete check
     private boolean completeCheck = true;

     public boolean isCompleteCheck() {
         return completeCheck;
     }

     public void setCompleteCheck(boolean completeCheck) {
         this.completeCheck = completeCheck;
     }

     public synchronized void addInvalidComponent(JComponent component) {
         checkThreadViolations(component);
         super.addInvalidComponent(component);
     }

     public void addDirtyRegion(JComponent component, int x, int y, int w, int 
h) {
         checkThreadViolations(component);
         super.addDirtyRegion(component, x, y, w, h);
     }

     private void checkThreadViolations(JComponent c) {
         if (!SwingUtilities.isEventDispatchThread() && (completeCheck || 
c.isShowing())) {
             Exception exception = new Exception();
             boolean repaint = false;
             boolean fromSwing = false;
             StackTraceElement[] stackTrace = exception.getStackTrace();
             for (StackTraceElement st : stackTrace) {
                 if (repaint && st.getClassName().startsWith("javax.swing.")) {
                     fromSwing = true;
                 }
                 if ("repaint".equals(st.getMethodName())) {
                     repaint = true;
                 }
             }
             if (repaint && !fromSwing) {
                 //no problems here, since repaint() is thread safe
                 return;
             }
             exception.printStackTrace();
         }
     }
}

JComponent Children Overlap

Another possible source of painting problems can occur if you allow children of a JComponent to overlap.

In this case, the parent must override isOptimizedDrawingEnabled to return false. If you do not override isOptimizedDrawingEnabled, then components can randomly appear on top of others, depending upon which component repaint was invoked on.

Display Update

Another source of painting problems can occur if you do not invoke repaint correctly when you need to update the display.

Changing a visible property of a Swing component, such as the font, will trigger a repaint or revalidate. If you are writing a custom component, then you must invoke repaint and possibly revalidate whenever the display or sizing information is updated. If you do not, the display will only update the next time someone triggers a repaint.

A good way to diagnose this is to resize the window. If the content appears after a resize, then that implies that the component did not invoke repaint or revalidate correctly.

Model Change

Invoke repaint when you change a visible property of a Swing component, but you need not invoke repaint when your model changes.

If your model sends out the correct change notification, the JComponent will invoke repaint or revalidate as appropriate.

However, if you change your model but do not send out a notification, then a repaint event may not even work. In particular this will not work with JTree. The correct thing to do is to send the appropriate model notification. This can usually be diagnosed by resizing the window and noticing that the display did not update correctly.

Add or Remove Components

When you add or remove components, you must manually invoke repaint or revalidate Swing and AWT.

Opaque Override

Another possible area of painting problems is if a component does not override opaque.

Further, if you do not invoke implementation you must honor the opaque property, that is, if this component is opaque, you must completely fill in the background with a non-opaque color. If you do not honor the opaque property, then you will likely see visual artifacts.

The only way to check for this is to look for consistent visual artifacts when the component invokes repaint.

Permanent Changes to Graphics

Do not make any permanent changes to a Graphics object that is passed to paint, paintComponent, or paintChildren.

Note:

If you override the graphics in a subclass, then you should not make permanent changes to the paint, paintComponent, or paintChildren passed in the Graphics object. For example, you should not alter the clip Rectangle or modify the transform. If you need to do these operations you may find it easier to create a new Graphics object from the passed in Graphics object and manipulate it instead.

If you ignore this restriction, then the result will be clipping or other weird visual artifacts.

Custom Painting and Double Buffering

Although you can override paint and do custom painting in the override, you should instead override paintComponent.

The JComponent.paint method ensures that painting happens to the double buffer. If you override paint directly, then you may lose double buffering.

Opaque Content Pane

Swing's painting architecture requires an opaque content pane.

The painting architecture of Swing requires an opaque JComponent to exist in the containment hierarchy above all other components. This is typically provided by using the content pane. If you replace the content pane, it is recommended that you make the content pane opaque by using setOpaque(true). Additionally, if the content pane overrides paintComponent, then it will need to completely fill in the background in an opaque color in paintComponent.

Renderer Call for Each Cell Performance

Renderers are painted for each cell, so ensure that the renderer does as little as possible.

Any slowdown in the renderer is magnified across all cells. For example, if you repaint the visible region of a table with 50x20 visible cells, then there will be 1000 calls to the renderer.

Possible Leaks

If the life cycle of your model is longer than that of a window with a component using the model, you must explicitly set the model of the Swing component to null.

If you do not set the model to null, your model will retain a reference to the Component, which will keep all components in the window from being garbage collected. Take a look at the following example.

TableModel myModel = ...;
JFrame frame = new JFrame();
frame.setContentPane(new JScrollPane(new JTable(myModel)));
frame.dispose();

If your application still holds a reference to myModel, then frame and all its children will still be reachable by way of the listener JTable installations on myModel. The solution is to invoke table.setModel(new DefaultTableModel()).

Mix Heavyweight and Lightweight Components

Mixing heavyweight and lightweight components can work in certain scenarios, as long as the heavyweight component does not overlap with any existing Swing components.

For example, a heavyweight will not work in an internal frame, because when the user drags around the internal frame it will overlap with other internal frames. If you use heavyweights, then invoke the following methods:

  • JPopupMenu.setDefaultLightWeightPopupEnabled(false)
  • ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false)

Use Synth

Synth is an empty canvas.

To use Synth, you must either provide a complete XML file that configures the look and feel, or extend SynthLookAndFeel and provide your own SynthStyleFactory.

Track Activity on Event Dispatch Thread

If a Swing application tries to do too much on the event dispatch thread, then the application will appear sluggish and unresponsive.

One way to detect this situation is to push a new EventQueue that can output logging information if an event takes too long to process. This approach is not perfect in that it has problems with focus events and modality, but it is good for ad-hoc testing.

Specify Default Layout Manager

Problems can be caused by differing default layout manager classes on a Swing component.

For example, the default for the JPanel class is FlowLayout, but the default for the JFrame class is BorderLayout. This situation is easily fixed by specifying a LayoutManager.

Listener Object Dispatched to Incorrect Component

MouseListener objects are dispatched to the deepest component that has MouseListener objects (or has enabled MouseEvent objects).

A ramification of this is if you attach a MouseListener to a component whose descendants have MouseListener objects, your MouseListener object will never get called.

This is easily reproduced with a composite component, like an editable JComboBox. Because a JComboBox has child components that have a MouseListener, a MouseListener attached to an editable JComboBox will never get notified.

If your MouseListener suddenly stops getting events, then it could be the result of a change in the application whereby a descendant component now has a MouseListener. A good way to check for this is to iterate over the descendants asking if they have any mouse listeners.

A similar scenario occurs with the KeyListener class. A KeyListener object is dispatched only to the focused component.

The JComboBox case is another example of this situation. In the editable JComboBox case the editor gets focus, not the JComboBox. As a result, a KeyListener attached to an editable JComboBox will never get notified.

Add a Component to Content Pane

You must add a JFrame, JWindow, or JDialog component to the content pane.

A component added to a top-level Swing component must go to the content pane, but the add method (and a couple of other methods) on the JFrame, JWindow, and JDialog classes redirect to the content pane. In other words, frame.getContentPane().add(component) is the same as frame.add(component).

The following methods redirect to the content pane for you: add (and its variants), remove (and its variants), and setLayout.

This is purely a convenience, but can cause confusion. In particular, getChildren, getLayout, and various others do not redirect to the content pane.

This change affects LayoutManagers that only work with one component, such as GroupLayout and BoxLayout. For example, new GroupLayout(frame) will not work; instead, you must use GroupLayout(frame.getContentPane()).

Drag and Drop Support

When using Swing you should use Swing's drag-and-drop support as provided by TransferHandler.

One Parent for a Component

Remember that a component can only exist in one parent at a time.

Problems occur when you share menu items between menus. For example, JMenuItem is a component, and therefore can exist in only one menu at a time.

JFileChooser Issues with Windows Shortcuts

The JFileChooser class does not support shortcuts on Windows OS (.lnk files).

Unlike the standard Windows file choosers, JFileChooser does not allow the user to follow Windows shortcuts when browsing the file system, because it does not show the correct path to the file.

To reproduce the problem, follow these steps:

  1. Create a text file on the Desktop called, for example, MyFile.txt. Open the text file and type some text, for example: This is the contents of MyFile.txt.
  2. Create a shortcut to the new text file in the following way: Drag the file with the right mouse button to another location on the Desktop and choose Create Shortcut(s) here.
  3. Run the JfileChooser test application, browse the Desktop, select Shortcut to MyFile.txt and click Open.
  4. The result file is PathToDesktop\Shortcut to MyFile.txt.lnk, but it should be PathToDesktop\MyFile.txt.
  5. In addition, the contents of the result file in the text area shows the contents of the file shortcut to MyFile.txt.lnk, but the contents should be This is the contents of MyFile.txt, which was typed in step 1.