Adding Drag-and-Drop Behavior to Graphics Nodes

By Josh Marinacci, September 26, 2008

JavaFX software enables you to easily add behavior to graphics nodes by using just a few lines of code. This example shows you two ways to add dragging to images.

Understanding the Code

The simplest way to make a shape draggable is to override the onMousePressed, and the onMouseDragged event handlers. The code in Figure 1 subclasses ImageView to add two event handlers. The onMousePressed handler saves the location of the mouse click, adjusted to account for the current location of the image by subtracting translateX and translateY. In the onMouseDragged event, the code moves the image's location by using the new x and y of the mouse, adjusted by the saved location.

Source Code
public class DraggableImage extends ImageView {
    public-init var maxX = 200;
    public-init var maxY = 200;
    var startX = 0.0;
    var startY = 0.0;
    
    override var onMousePressed = function(e:MouseEvent):Void {
        startX = e.sceneX-translateX;
        startY = e.sceneY-translateY;
    }
    
    override var onMouseDragged = function(e:MouseEvent):Void {
        var tx = e.sceneX-startX;
        if(tx < 0) { tx = 0; }
        if(tx > maxX-image.width) { tx = maxX-image.width; }
        translateX = tx;

        var ty = e.sceneY-startY;
        if(ty < 0) { ty = 0; }
        if(ty > maxY-image.height) { ty = maxY-image.height; }
        translateY = ty;
    }
}

Figure 1: DraggableImage.fx Class

To use this new DraggableImage just create an instance of the desired size and put it in your stage, as shown in Figure 2. The results should look like the applet in Figure 3.

Source Code
Stage {
    scene: Scene {
        content: Group {
            content: [
                ImageView { image: Image { url: "{__DIR__}images/background.png" } },
                DraggableImage {
                    maxX: 240
                    maxY: 320
                    image: Image { url: "{__DIR__}images/ball.png" }
                }
            ]
        }
    }
}

Figure 2: Using DraggableImage

Figure 3: DraggableImage Applet

Reusable Dragging

The previous example works quite well but it requires you to subclass each shape that you want to drag. This strategy won't work if you need many different kinds of draggable shapes. Instead you can create a reusable DragBehavior class that adds dragging to any node, as shown in Figure 4.

Source Code
public class DragBehavior {
    public var target: Node;
    public-init var maxX = 200;
    public-init var maxY = 200;
    var startX = 0.0;
    var startY = 0.0;
    
    init {
        target.onMousePressed = function(e:MouseEvent):Void {
            startX = e.sceneX-target.translateX;
            startY = e.sceneY-target.translateY;
        }
        target.onMouseDragged = function(e:MouseEvent):Void {
            var tx = e.sceneX-startX;
            if(tx < 0) { tx = 0; }
            if(tx > maxX-target.boundsInLocal.width) { tx = maxX-target.boundsInLocal.width; }
            target.translateX = tx;

            var ty = e.sceneY-startY;
            if(ty < 0) { ty = 0; }
            if(ty > maxY-target.boundsInLocal.height) { ty = maxY-target.boundsInLocal.height; }
            target.translateY = ty;
        }
    }
}

Figure 4: DragBehavior.fx Class

The DragBehavior class provides the same capabilities as the previous example in Figure 2, but it adds the handlers to whichever target Node it is given. To use the DragBehavior class, just set the target node to whichever graphics object you want to move, as Figure 5 shows. The dragging behavior is the same, as shown in the applet in Figure 6.

Source Code
var ball = ImageView {
    image: Image { url: "{__DIR__}images/ball.png" }
    };
var drag = DragBehavior { target: ball maxX: 240 maxY: 320 };

Stage {
    scene: Scene {
        content: [
            ImageView { image: Image { url: "{__DIR__}images/background.png" } },
            Group { content: ball },
            ]
    }
}

Figure 5: Using DragBehavior Class

Figure 6: DragBehavior Applet