Copyright ©2006 By Keith Rimington

Screenshot Image - two rectangular JLabels act as a DragSource and a DropTarget

Introduction

While writing a computer variant of Milton Bradley's famous game, BattleShip, I wanted to allow the user to drag and drop his/her own ships onto the grid in very much the same way that it would be done in real life. This required Java's powerful java.awt.dnd and java.awt.dnd.datatransfer API. Finding that there seemed to be only a little information on the internet about drag and drop event handling in Java, I prepared this demonstration.

Responsive interaction with the application and the mouse is important when writing graphical applications, such as a games. As the complexity of our data structures grow, and the requirements of our interface to communicate to the user increase, having a good mechanism to do Drag and Drop (DND) operations is vital. Because many things can happen during a drag operation, including data transfer, failed drags, and crossing in, over, and out of controls, Java provides a comprehensive event-handling API for drag and drop.

What's in the Demo?

This article explores drag and drop within single applications. The demo is a simple window with two custom controls. One control is configured to be the source of drag operations, the other the target. Additionally, the data transferred between controls is a custom class containing both a String and a Color. Knowing how to handle such a simple class gives us the capability of transfering any object of any class!

Will will focus on the following seven classes and interfaces:

  • The DataFlavor class to define custom data types.
  • The Transferable Interface, which defines packages of transferable data.
  • The DragSource which we will include in a component we wish to be the source of a drag.
  • The DropTarget which we will inclide in a component we wish to be the target of a drag.
  • The DragGestureListener which fires handlers when the drag operation begins
  • The DragSourceListener Interface, which defines objects that handle five events related to drag and drop over-effects.
  • The DropTargetListener Interface, which defines objects that handle five events related to drag and drop under-effects.

The source code included with this project contains almost line-by-line comments describing each step in further detail.

Over-effects and Under-effects

Before looking at the code, it is fitting to say a little about drag effects. Over-effects and under-effects are two different ways that a GUI can communicate to the user what is taking place. Over-effects are usually cursor changes generated by the source of the drag whenever the drag state changes or updates. Under-effects are usually generated by targets to add to the drag effect.

For example, in some editors that support dragging text from one document to another,the source document may choose whether to display a move cursor or a cancel cursor. This is an over-effect. The target document may display a caret at the point of insertion in the document. This is an under-effect.

By separating the roles of the source and the target, neither is dependent on the actions of the other to ensure that the appropriate message is displayed to the user.

Creating a Transferable and DataFlavors

Java's Transferable interface provides a way to wrap up any class and label it for transfer. In the demo, we will wrap up a Color object and a String. The first thing we will do is make a custom DataFlavor for our data. A DataFlavor is a label that identifies what kind of object is being dragged. By giving our data a DataFlavor the target can discriminate between which flavors it can receive.

public class DemoTransferable
{
    // This is the DataFlavor we will associate with Color\n
    public static final DataFlavor customColorFlavor =
        getCustomColorFlavor();

    // This is the DataFlavor we will associate with String
    public static final DataFlavor customStringFlavor =
        getCustomColorFlavor();

    // ...

    // This method creates a flavor for Color.
    public static DataFlavor getCustomColorFlavor() {
      DataFlavor temp = null;
      try {
        temp = new DataFlavor(
            DataFlavor.javaJVMLocalObjectMimeType +
            "";class=java.awt.Color"");
        } catch(ClassNotFoundException e) {
          // Handle the exception
      }
      return temp;
    }

    // This method creates a flavor for String
    public static DataFlavor getCustomStringFlavor() {
      DataFlavor temp = null;
      try {
        temp = new DataFlavor(
            DataFlavor.javaJVMLocalObjectMimeType +
            "";class=java.lang.String"");
        } catch(ClassNotFoundException e) {
          // Handle the exception
        }
      return temp;
    }
}

The static String, DataFlavor.javaJVMLocalObjectMimeType identifes our flavor as one that must stay within the application. This can be useful if you want to restrict the ability to transfer data from one instance of the application to another.

The remaining three methods are required by the Transferable interface. They are used to broadcast which DataFlavor types are available and do the data transfer.

  // ...

  // Provide an array of supported flavors
  public synchronized DataFlavor [] getTransferDataFlavors() {
    return new DataFlavor [] {
        customColorFlavor,
        customStringFlavor };
  }

  // Answer inquiries on supported flavors
  public boolean isDataFlavorSupported(DataFlavor flavor) {
    return (
        flavor.match(customColorFlavor) ||
        flavor.match(customStringFlavor));
  }

  // Do data transfer
  public synchronized Object getTransferData(DataFlavor flavor)
        throws UnsupportedFlavorException {
    if(flavor.match(customColorFlavor))
      return color;
    else if(flavor.match(customStringFlavor))
      return string;
    else throw new UnsupportedFlavorException(flavor);
  }

  // ...

Creating a Drag Source

Now we will add functionality to a JLabel to allow it to be the source of a drag operation. To do this, we will embed a DragSource object in it.

public class SourceLabel extends JLabel {
  // This object makes our JLabel a drag source.
  protected DragSource dragSource;

  // ...

Now we must give the dragSource a DragGestureListener . This object will respond to the event that is a combination of a mouse down event and a mouse move event. Before we associate this listener with dragSource, we will implement the interface in a concrete class. Furthermore, our new DemoDragGestureListener class delegates the remaining drag and drop events to a DragSourceListener. Both class are implemented below:

  private class DemoDragGestureListener
        implements DragGestureListener {

    /**
     * This method is invoked when a drag gesture begins.
     * Our main goal here is to begin the drag process and
     * attach a {@code DragSourceListener}.
     */
    public void dragGestureRecognized(DragGestureEvent e) {

      // modifiers includes information about the mouse click.
      int modifiers = e.getTriggerEvent().getModifiers();

      // Send black text on a left click.
      if((modifiers & InputEvent.BUTTON1_MASK) != 0) {

        // The startDrag method attaches a DemoDragSourceListener
        // to handle the remaining dnd events.
        e.startDrag(
            DragSource.DefaultMoveNoDrop,
            new DemoTransferable(
                ""Transferred Text."",
                Color.black),
            new DemoDragSourceListener());
      }
      // Send randomly colored text on a right click.
      else if((modifiers & InputEvent.BUTTON3_MASK) != 0) {

        // Do the same thing, but with a random color ...
      }
    }
  }

  private class DemoDragSourceListener
        implements DragSourceListener {

    /**
     * dragEnter is fired whenever the cursor enters a new component.
     *
     * Here we will determine if the target component is allowing
     * a move action, and update the cursor appropriately.
     */
    public void dragEnter(DragSourceDragEvent e){
      int action = e.getDropAction();
      if((action & DnDConstants.ACTION_MOVE)!=0)
        e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
      else e.getDragSourceContext()
            .setCursor(DragSource.DefaultMoveNoDrop);
    }

    /**
     * dragOver is fired rapidly whenever the cursor moves within
     * a component.
     *
     * Here we will determine if the target component is allowing
     * a move action, and update the cursor appropriately.
     */
    public void dragOver(DragSourceDragEvent e){
      int action = e.getDropAction();
      if((action & DnDConstants.ACTION_MOVE)!=0)
        e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
      else e.getDragSourceContext()
            .setCursor(DragSource.DefaultMoveNoDrop);
    }

    /**
     * dragExit is fired whenever the cursor exits a component.
     */
    public void dragExit(DragSourceEvent e){
      e.getDragSourceContext()
            .setCursor(DragSource.DefaultMoveNoDrop);
    }

    /**
     * dragDropEnd is called whenever the dnd action concludes.
     *
     * Here we simply change the text on the label
     * to reflect the success of the operation.
     */
    public void dragDropEnd(DragSourceDropEvent e){
      if(e.getDropSuccess())
        SourceLabel.this.setText(""The drop succeeded!"");
      else
        SourceLabel.this.setText(""The drop failed."");
    }

    /**
     * dropActionChanged is an opportunity to unify actions when the
     * drop action changes.  For our purposes, it wasn't necessary to
     * do anything here.
     */
    public void dropActionChanged (DragSourceDragEvent e){
    }
  }

All that's left to do for the drag source is to hook up these internal classes in our SourceLabel class.

Creating the Drop Target

Now all we need to do is create a drop target that can receive our custom class. We will add this functionality to another JLabel.

public class TargetLabel extends JLabel {

  // This gives our class DropTarget abilities.
  protected DropTarget dropTarget;


  // We will define this class below.
  protected DemoDropTargetListener demoDropTargetListener;

  // ...

DemoDropTargetListener implements the DropTargetListener interface to handle the five drag and drop events. We will implement the class below:

  private class DemoDropTargetListener
        implements DropTargetListener {

    /** True indicates dragging should be painted. */
    boolean dragging=false;

    /** The string being transfered. */
    String string;

    /** The color being transfered. */
    Color color;

    /** The location of the dragging cursor. */
    Point dragLocation = null;

    /**
     * dragEnter is called whenever the cursor enters this  component.
     * Our under effect will be a string drawn in the designated color.
     */
    public void dragEnter(DropTargetDragEvent e) {
      if(!isValidData(e)) {
        e.rejectDrag();
        dragging=false;
        repaint();
        return;
      }
      e.acceptDrag(DnDConstants.ACTION_MOVE);
      dragging = true;
      dragLocation = e.getLocation();
      color = getColor(e);
      string = getString(e);
      repaint();
    }

    /**
     * dragOver is called when the cursor moves within this component.
     * dragOver allows us to update the location of the string.
     */
    public void dragOver(DropTargetDragEvent e) {
      if(!isValidData(e)) {
        e.rejectDrag();
        dragging=false;
        repaint();
        return;
      }
      e.acceptDrag(DnDConstants.ACTION_MOVE);
      dragging = true;
      dragLocation = e.getLocation();
      repaint();
    }

    /**
     * dropActionChanged is an opportunity to unify actions when the
     * drop action changes.  For our purposes, it isn't necessary to
     * do anything here.
     */
    public void dropActionChanged(DropTargetDragEvent e) {
    }

    /**
     * dragExit is called whenever the cursor exits this component.
     * Our under-effects should quit immediately.
     */
    public void dragExit(DropTargetEvent e) {
      dragging = false;
      repaint();
    }

    /**
     * This is where the drop magic actually occurs.
     * Note: The reason we do not call getColor now is that we
     * already have it from the DragEnter event.
     */
    public void drop(DropTargetDropEvent e) {
      if(!isValidData(e)) {
        e.rejectDrop();
        dragging=false;
        repaint();
        return;
      }
      e.dropComplete(true);
      dragging=false;
      TargetLabel.this.setBackground(color);
      TargetLabel.this.setText(string);
      repaint();
    }

    // ...

    // isValidData(), getColor, and getString are defined in the source.

    // ...

  }

Now a few lines of code to hook our new class up into DropLabel .

  public TargetLabel() {

    // ...

    demoDropTargetListener = new DemoDropTargetListener();

    /**
     * This constructor takes four argments, which are:
     * The listening component.
     * The allowed drop actions.
     * The listener who will monitor drag and drops.
     * A flag indicating the target will immediately listen.
     */
    dropTarget = new DropTarget(
        this,
        DnDConstants.ACTION_MOVE,
        demoDropTargetListener,
        true);
  }

  public void paint(Graphics g) {
    super.paint(g);
    if(demoDropTargetListener.dragging) {
      g.setColor(demoDropTargetListener.color);
      g.drawString(
          demoDropTargetListener.string,
          demoDropTargetListener.dragLocation.x,
          demoDropTargetListener.dragLocation.y);
    }
  }

Wrapping it all up

Now the two classes are complete and all that is left is to place them in a Frame and watch them play out! Using a similar setup to this I was able to transfer images of ships across panels and other components and render them on the components or the glass pane.

I hope you found this article useful. Especially because this is my first public article, your feedback is welcome. Thank you for reading.

This article was published on CodeProject


 
Categories: Java

June 1, 2007
@ 10:36 PM

At long last, Notes On Code arrives to the Web. So many great Web contributors have helped me out that I hope in some small way to give back.

My thanks to you all.


 
Categories: Misc