Chapter 4

Troubleshooting 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.

4.1 General Debugging Tips for Swing

Swing's painting infrastructure changed quite extensively in Java SE 6. If you notice painting artifacts specific to Java SE 6 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 Linux and Solaris. For more information, see the following bug in the Bug Database.

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6517045

Some common Swing problems:

  • Painting problems.

  • Renderers.

  • Updating models from wrong thread.

  • Hangs.

  • Responsiveness.

  • Repainting issues.

  • isOpaque usage.

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

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.

Identifying bad renderers:

  • Sluggish application, especially when scrolling.

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

4.2 Specific Debugging Tips for Swing

The following subsections present some tips for troubleshooting Swing problems.

4.2.1 Incorrect Threading

Random exceptions and painting problems are usually the result of incorrect threading usage of 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 way of an instrumented RepaintManager, as illustrated by the following code:

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();
         }
     }
}

4.2.2 Overlapping Children of a JComponent

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, components can randomly appear on top of others, depending upon which one repaint was invoked on.

4.2.3 Updating the Display

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, you must invoke repaint and possibly revalidate whenever the display or sizing information has been 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, it implies that the component did not invoke repaint or revalidate correctly.

4.2.4 Changing the Model

Just as you do not need to invoke repaint when you change a visible property of a Swing component, you also 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, a repaint event may not even work. In particular this will not work with JTree. The correct thing to do is to send out the appropriate model notification. This can usually be diagnosed by again resizing the window and noticing that the display has not updated correctly.

4.2.5 Adding or Removing Components

When you add or remove components, you need to invoke repaint or revalidate. Swing and AWT will not invoke repaint or revalidate in these situations, and therefore you must invoke them yourself.

4.2.6 Overriding Opaque

Another possible area of painting problems is if a component does not override opaque. Here is the documentation warning on this:

Further, if you do not invoker super's implementation you must honor the opaque property, that is if this component is opaque, you must completely fill in the background in a non-opaque color. If you do not honor the opaque property 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.

4.2.7 Permanent Changes to a Graphics

Do not make any permanent changes to a Graphics passed to paint, paintComponent, or paintChildren. Here is the documentation warning on this:

If you override this in a subclass you should not make permanent changes to the passed in Graphics. 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 from the passed in Graphics and manipulate it.

Not honoring this restriction will result in clipping or other weird visual artifacts.

4.2.8 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, you may lose double buffering.

4.2.9 Opaque Content Pane

Swing's painting architecture requires an opaque content pane. Here is the documentation:

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

4.2.10 Performance: Call to Renderer for Each Cell

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, there will be 1000 calls to the renderer.

4.2.11 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. For example, consider the following code:

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 installs on myModel. The solution is to invoke table.setModel(new DefaultTableModel()).

4.2.12 Mixing Heavyweight and Lightweight Components

Mixing heavyweight and lightweight components can work in certain scenarios, primarily 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 do use heavyweights, invoke the following methods:

  • JPopupMenu.setDefaultLightWeightPopupEnabled(false)

  • ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false)

4.2.13 Tips for Using 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.

4.2.14 Tracking Activity on Event Dispatch Thread

If a Swing application tries to do too much on the event dispatch thread, 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.

4.2.15 Differing Default Layout Managers

Problems can be caused by having 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.

4.2.16 Listener Objects Dispatched to Deepest Component

MouseListener objects are dispatched to the deepest component that has MouseListener objects (or has enabled MouseEvent objects). A ramification of this is that 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, 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.

4.2.17 Adding a Component to Content Pane

Prior to J2SE 1.5 you could not add a component to a JFrame, JWindow, JDialog or JApplet. Instead, you needed to add the component to the content pane. As of J2SE 1.5 it is still the case that 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 these classes redirect to the content pane. In other words, doing 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 impacts LayoutManagers that only work with one component, such as GroupLayout and BoxLayout. For example, new GroupLayout(frame) will not work; instead, you need to do new GroupLayout(frame.getContentPane()).

4.2.18 Drag and Drop Support in Swing

When using Swing you should use Swing's drag–and–drop support as provided by TransferHandler. Otherwise, you will have to manage the selection and various other issues.

4.2.19 One Parent at a Time for a Component

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

4.2.20 Problem With JFileChooser and Shortcuts on Windows

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, perform the following procedure:

  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 press 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 the first step.