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 terminate the X server for Linux.
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
, andJList
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
Specific debugging tips for Swing and provides examples for possible issues and workarounds.
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, 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, 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
passed to paint
, paintComponent
, or paintChildren
.
Note:
If you override the graphics in a a subclass, then you should not make permanent changes to the paint
, paintComponent
, or paintChildren
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.
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 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, 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:
- 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 toMyFile.txt
and click Open. - The result file is
PathToDesktop\Shortcut
toMyFile.txt.lnk
, but it should bePathToDesktop\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 beThis is the contents of MyFile.txt
, which was typed in step 1.