Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API
JTrees has been covered already in Java Tip 97: Add Drag and Drop to Your JTrees. I encourage you to read that tip first to get a good understanding of the drag-and-drop paradigm.The code I present in this tip has the following design goals:
JTree node being dragged
JTree nodes right and left without keyboard interaction
As a bonus, I have included support for the Autoscroll interface so that the JTree content is automatically scrolled when the mouse nears the viewport's edge during a drag operation. This is implemented as
described in Eckstein, Loy, and Wood's Java Swing, which explains the autoscrolling process adequately, so I won't elaborate here.
Arguably, these capabilities should all be part of a Swing look-and-feel implementation, but I found them easier to implement
as part of a JTree class extension. In spite of the number of goals, the actual code involved is quite minimal.
Figure 1 is an example of what you would see during a drag operation after implementing this tip -- the food folder is about to be dropped after the football node.

Figure 1. Screenshot of CTree drag image in action
To emulate this JTree behavior, you must change both the DragGestureListener and the DropTargetListener classes.
You add code to the DragGestureListener's dragGestureRecognized() method to figure out which JTree node is being dragged, and then build a BufferedImage to represent the node during the drag operation.
First, find out where the mouse is clicked relative to the selected tree node's bounding rectangle. You'll need this later to keep the drag image positioned at the same distance from the mouse pointer as the node is being dragged.
Point ptDragOrigin = e.getDragOrigin();
TreePath path = getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
Rectangle raPath = getPathBounds(path);
m_ptOffset.setLocation(ptDragOrigin.x-raPath.x, ptDragOrigin.y-raPath.y);
Now, ask the tree cell renderer (if you are using the DefaultTreeCellRenderer, the renderer is a JLabel) to render itself into a BufferedImage, using a Graphics2D graphics context set up to create a semi-transparent image. The result is a ghosted version of the original tree node that
won't interfere with the ability to see the underlying JTree nodes as they are dragged over.
// Get the tree cell renderer
JLabel lbl = (JLabel) getCellRenderer().getTreeCellRendererComponent
(
this, // tree
path.getLastPathComponent(), // value
false, // isSelected
isExpanded(path), // isExpanded
getModel().isLeaf(path.getLastPathComponent()), // isLeaf
0, // row
false // hasFocus
);
// The layout manager normally does this...
lbl.setSize((int)raPath.getWidth(), (int)raPath.getHeight());
// Get a buffered image of the selection for dragging a ghost image
_imgGhost = new BufferedImage
(
(int)raPath.getWidth(),
(int)raPath.getHeight(),
BufferedImage.TYPE_INT_ARGB_PRE
);
// Get a graphics context for this image
Graphics2D g2 = _imgGhost.createGraphics();
// Make the image ghostlike
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f));
// Ask the cell renderer to paint itself into the BufferedImage
lbl.paint(g2);
To make the ghosted JLabel look more flash, you also paint under the text with a GradientPaint that fades from left to right:
// Locate the JLabel's icon so you don't paint under it
Icon icon = lbl.getIcon();
int nStartOfText =
(icon == null) ? 0 : icon.getIconWidth()+lbl.getIconTextGap();
// Use DST_OVER to cause under-painting to occur
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, 0.5f));
// Use system colors to match the existing decor
g2.setPaint(new GradientPaint(nStartOfText, 0, SystemColor.controlShadow,
getWidth(), 0, new Color(255,255,255,0)));
// Paint under the JLabel's text
g2.fillRect(nStartOfText, 0, getWidth(), _imgGhost.getHeight());
// Finished with the graphics context now
g2.dispose();
Finally, wrap the TreeNode about to be dragged in a Transferable class and invoke startDrag(). Note that you still pass the drag image to the startDrag() method because the underlying platform uses it if it can.
// Wrap the path being transferred into a Transferable object
Transferable transferable = new CTransferableTreePath(path);
// Remember the path being dragged (you may want to delete it later)
_pathSource = path;
// Pass the drag image just in case the platform IS supports it
e.startDrag(null, _imgGhost, new Point(5,5), transferable, this);
All the drag-image painting happens in the DropTargetListener's dragOver() method. You only need to draw the image if the platform does not already do it for you, which you find out by first calling
the DragSource.isDragImageSupported() static method.
To draw the drag image, you must do at least two things: First, repaint the real estate the drag image last occupied. Note
that simply calling repaint() won't work because it effectively delays the repainting, possibly until after you have drawn the new drag image, and therefore,
erases all or part of it. You really must paint the area immediately, using the, you guessed it, paintImmediately() method.
Second, you draw the ghost image in its new location. Note that you draw the image the same distance away from the mouse pointer as when the node was first clicked.
Graphics2D g2 = (Graphics2D) getGraphics();
if (!DragSource.isDragImageSupported())
{
// Erase the last ghost image and cue line
paintImmediately(_raGhost.getBounds());
// Remember where you are about to draw the new ghost image
_raGhost.setRect(pt.x - _ptOffset.x, pt.y - _ptOffset.y,
_imgGhost.getWidth(), _imgGhost.getHeight());
// Draw the ghost image
g2.drawImage(_imgGhost,
AffineTransform.getTranslateInstance(_raGhost.getX(),
_raGhost.getY()),
null);
}
In addition, you create a ghosted cue line. A cue line is a line temporarily drawn under each prospective drop target, giving the user a better idea of which node is the current drop target.
// Get the drop target's bounding rectangle
Rectangle raPath = getPathBounds(path);
// Cue line bounds (2 pixels beneath the drop target)
_raCueLine.setRect(0, raPath.y+(int)raPath.getHeight(), getWidth(), 2);
g2.setColor(_colorCueLine); // The cue line color
g2.fill(_raCueLine); // Draw the cue line
To enable automatic expanding and collapsing of tree nodes as the user's mouse hovers over them, you need to restart a timer each time the tree selection changes.
TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (!(path == _pathLast))
{
_pathLast = path;
_timerHover.restart();
}
If the timer pops, then you toggle the expanded/collapsed state of the last selected tree path. The timer is set up in the
DropTargetListener constructor. The following code kicks in after about 1,000 milliseconds (1 second) of loitering.
_timerHover = new Timer(1000, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
_nLeftRight = 0; // Reset left/right movement trend
if (isRootPath(_pathLast))
return; // Do nothing if you are hovering over the root node
if (isExpanded(_pathLast))
collapsePath(_pathLast);
else
expandPath(_pathLast);
}
});
Our next design goal is to recognize some mouse gestures. If the user drags the mouse sufficiently far to the right, then the dragged tree node must be inserted under rather than after the drop target tree node. Similar, but reverse, logic applies to a sufficiently long drag to the left.
Your TreeModel must actually insert the node in the appropriate place. Our task here is to recognize the gesture and pass the hint to the
model. You can do this in the dragOver() method after you determine that the mouse pointer has actually moved.
int nDeltaLeftRight = pt.x - _ptLast.x;
if ((_nLeftRight > 0 && nDeltaLeftRight < 0)
|| (_nLeftRight < 0 && nDeltaLeftRight > 0) )
_nLeftRight = 0;
_nLeftRight += nDeltaLeftRight;
I have found that on my system a 20-pixel horizontal movement equates to a "flick." The flick gesture sends a visual signal
to the user by overlaying the ghost image with an arrow pointing to either the left or right as required. (Arrow images are
built by the CArrowImage class, which saves having to ship image files with your application.)
if (_nLeftRight > 20)
{
g2.drawImage(_imgRight,
AffineTransform.getTranslateInstance(pt.x - _ptOffset.x,
pt.y - _ptOffset.y), null);
_nShift = +1;
}
else if (_nLeftRight < -20)
{
g2.drawImage(_imgLeft,
AffineTransform.getTranslateInstance(pt.x - _ptOffset.x,
pt.y - _ptOffset.y), null);
_nShift = -1;
}
else
_nShift = 0;
This tip resolves an annoying inconsistency in the cross-platform implementation of drag and drop. Users increasingly expect strong visual feedback when performing drag-and-drop operations, especially when they doubt what will happen when they release the mouse button!
The code presented here is a one-stop shop for the following drag-and-drop features:
There is plenty of room for enhancing this code. For example, you can graphically represent a multiple-selection drag, animated drag images, snail trails, and other eye candy; I shall leave those to readers with more time on their hands!
Free Download - 5 Minute Product Review. When slow equals Off: Manage the complexity of Web applications - Symphoniq
![]()
Free Download - 5 Minute Product Review. Realize the benefits of real user monitoring in less than an hour. - Symphoniq