[ Team LiB ] Previous Section Next Section

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.

    [ Team LiB ] Previous Section Next Section