This topic discusses some specific debugging tips for Swing and provides illustration examples for possible issues and workarounds. The following topics describe problems in Swing and troubleshooting techniques:
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 in Example 13-1.
Example 13-1 Incorrect Threading
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(); } } }
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.
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.
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.
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.
Another possible area of painting problems is if a component does not override opaque.
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.
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.
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.
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
.
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.
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. Look at Example 13-2.
Example 13-2 Possible leaks in Swing
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())
.
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)
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
.
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.
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
.
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.
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())
.
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.
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.
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:
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
.
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.
Run the JfileChooser
test application, browse the Desktop, select Shortcut to MyFile.txt and click Open.
The result File is PathToDesktop\Shortcut to MyFile.txt.lnk, but it should be PathToDesktop\MyFile.txt.
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.