14.2 A Clock with Drag and Copy Support
Another way to customize Swing drag-and-drop is to subclass a
Swing component, define new property accessor methods for it, and
then register a TransferHandler to transfer the
value of the new property. This is what we do in Example 14-2: we define a custom Swing component that
displays the current time and uses a
TransferHandler to make the contents of its new
time property available. Like Example 14-1, this program uses a
MouseMotionListener to detect drags. It also
defines a key binding so that Ctrl-C
copies the time to the clipboard. This example defines a custom
component, but not a main( ) method: use the
ShowBean program of Chapter 11
to display the component. You may want to run
ShowBean again to display a
JTextField or similar component, so that you have
somewhere to drop or paste the time values you've
dragged or copied. Also try dropping or pasting the value into other
non-Java applications (such as your text editor) that you have
running on your desktop.Example 14-2 also demonstrates the
javax.swing.Timer and
java.text.DateFormat classes, and shows how to use the (new in
Java 1.4) InputMap and
ActionMap Swing classes for associating key
bindings with components.
Example 14-2. DigitalClock.java
package je3.datatransfer;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import javax.swing.Timer; // disambiguate from java.util.Timer
import java.text.DateFormat;
import java.util.Date;
/**
* A custom Swing component that displays a simple digital clock.
* Demonstrates how to add copy and drag support to a Swing component
* with TransferHandler.
*/
public class DigitalClock extends JLabel {
DateFormat format; // How to display the time in string form
int updateFrequency; // How often to update the time (in milliseconds)
Timer timer; // Triggers repeated updates to the clock
public DigitalClock( ) {
// Set default values for our properties
setFormat(DateFormat.getTimeInstance(DateFormat.MEDIUM, getLocale( )));
setUpdateFrequency(1000); // Update once a second
// Specify a Swing TransferHandler object to do the dirty work of
// copy-and-paste and drag-and-drop for us. This one will transfer
// the value of the "time" property. Since this property is read-only
// it will allow drags but not drops.
setTransferHandler(new TransferHandler("time"));
// Since JLabel does not normally support drag-and-drop, we need an
// event handler to detect a drag and start the transfer.
addMouseMotionListener(new MouseMotionAdapter( ) {
public void mouseDragged(MouseEvent e) {
getTransferHandler( ).exportAsDrag(DigitalClock.this, e,
TransferHandler.COPY);
}
});
// Before we can have a keyboard binding for a Copy command,
// the component needs to be able to accept keyboard focus.
setFocusable(true);
// Request focus when we're clicked on
addMouseListener(new MouseAdapter( ) {
public void mouseClicked(MouseEvent e) { requestFocus( ); }
});
// Use a LineBorder to indicate when we've got the keyboard focus
addFocusListener(new FocusListener( ) {
public void focusGained(FocusEvent e) {
setBorder(LineBorder.createBlackLineBorder( ));
}
public void focusLost(FocusEvent e) { setBorder(null); }
});
// Now bind the Ctrl-C keystroke to a "Copy" command.
InputMap im = new InputMap( );
im.setParent(getInputMap(WHEN_FOCUSED));
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C,InputEvent.CTRL_MASK),
"Copy");
setInputMap(WHEN_FOCUSED, im);
// And bind the "Copy" command to a pre-defined Action that performs
// a copy using the TransferHandler we've installed.
ActionMap am = new ActionMap( );
am.setParent(getActionMap( ));
am.put("Copy", TransferHandler.getCopyAction( ));
setActionMap(am);
// Create a javax.swing.Timer object that will generate ActionEvents
// to tell us when to update the displayed time. Every updateFrequency
// milliseconds, this timer will cause the actionPerformed( ) method
// to be invoked. (For non-GUI applications, see java.util.Timer.)
timer = new Timer(updateFrequency, new ActionListener( ) {
public void actionPerformed(ActionEvent e) {
setText(getTime( )); // set label to current time string
}
});
timer.setInitialDelay(0); // Do the first update immediately
timer.start( ); // Start timing now!
}
// Return the current time as a String.
// This is the property accessor method used by the TransferHandler.
// Since there is a getter, but no setter, the TransferHandler will
// reject any attempts to drop data on us.
public String getTime( ) {
// Use the DateFormat object to convert current time to a string
return format.format(new Date( ));
}
// Here are two related property setter methods
public void setFormat(DateFormat format) { this.format = format; }
public void setUpdateFrequency(int ms) { this.updateFrequency = ms; }
}
When you try out this DigitalClock component, you
may notice a shortcoming: the TransferHandler
class calls getTime( ) when the time value is
dropped or pasted, not when it is originally dragged or copied. This
is counterintuitive, but it is how TransferHandler
works.