Skip to main content

How to drag nodes and windows?

Posted by malenkov on December 24, 2008 at 11:11 AM PST

Every JavaFX node is able to process mouse movement events. Thanks to this ability the user can easily drag nodes on the scene or move windows. However, there are some nuances worth mentioning...

It is very easy to create an application that could be dragged when you click on its contents. This feature is important for transparent windows and widgets which have no control elements provided by the window manager. For example, consider the following widget template.

var stage: Stage = Stage {
  style: StageStyle.TRANSPARENT
  scene: Scene {
    fill: null
    width: ...
    height: ...
    content: Group {
      content: ...
      onMouseDragged: function(event) {
        stage.x += event.dragX;
        stage.y += event.dragY;
      }

    }
  }
}

Now create a simple application with three draggable circles of different colors. Note that the dragX and dragY variables of the MouseEvent class provide offsets relative to the start of the dragging operation. By this reason, it is first necessary to store the initial position of the node.

VBox {
  translateX: 200
  translateY: 70
  spacing: 10
  content: for (color in colors)
    Circle {
      var x: Number;
      var y: Number;
      onMousePressed: function(event) {
        x = event.node.translateX;
        y = event.node.translateY;
      }

      onMouseDragged: function(event) {
        event.node.translateX = x + event.dragX;
        event.node.translateY = y + event.dragY;
      }
      fill: color
      radius: 50
    }
}

The example above has several issues. First, if you place two circles so that they overlap each other, you might be able to drag both circles simultaneously. To prevent this sort of behavior, you can set the blocksMouse variable to true. However, this setting blocks all mouse events. Second, all nodes are painted in the order of their addition to the group: the last added node is drawn over other nodes. Therefore, if you drag the first node, it can get hidden under another one. To solve this issue, create a new application which will change the order of objects in the group by placing the selected node at the end of the content sequence.

VBox {
  translateX: 200
  translateY: 70
  spacing: 10
  content: for (color in colors)
    Circle {
      var x: Number;
      var y: Number;
      onMousePressed: function(event) {
        x = event.node.translateX;
        y = event.node.translateY;
        event.node.toFront();
      }
      onMouseDragged: function(event) {
        event.node.translateX = x + event.dragX;
        event.node.translateY = y + event.dragY;
      }
      blocksMouse: true
      fill: color
      radius: 50
    }
}

One cannot but notice a strange behavior when pressing the mouse on the circle. The fact of the matter is that all nodes in the group are layed out all over again every time when the group content changes. So this approach is not recommended for a group that contains a draggable node and uses a layout mechanism. Even if you write some code that stores the location of the group's objects, the group bounds can be changed. The latter may result in laying out of objects in groups of higher levels. To solve this issue, create another application where draggable nodes belong to the top level group that does not lay out its content.

Group {
  translateX: 200
  translateY: -40
  var offset: Number;
  content: for (color in colors)
    Circle {
      var x: Number;
      var y: Number;
      onMousePressed: function(event) {
        x = event.node.translateX;
        y = event.node.translateY;
        event.node.toFront();
      }
      onMouseDragged: function(event) {
        event.node.translateX = x + event.dragX;
        event.node.translateY = y + event.dragY;
      }
      blocksMouse: true
      fill: color
      radius: 50
      centerY: offset += 110
    }
}

To summarize create a new application with the DragDropNode class that helps to make any node draggable.

Group {
  translateX: 200
  translateY: -40
  var offset: Number;
  content: for (color in colors)
    DragDropNode {
      node:
Circle {
        fill: color
        radius: 50
        centerY: offset += 110
      }
    }
}

Note that the DragDropNode class adds the Glow effect before dragging and removes it after.

class DragDropNode extends CustomNode {
  var node: Node;
  override function create() {
    Group {
      var x: Number;
      var y: Number;
      onMousePressed: function(event) {
        x = translateX;
        y = translateY;
        toFront();
        effect = Glow {
          level: 0.5
        }

      }
      onMouseDragged: function(event) {
        translateX = x + event.dragX;
        translateY = y + event.dragY;
      }
      onMouseReleased: function(event) {
        effect = null
      }

      blocksMouse: true
      content: bind node
    }
  }
}

Related Topics >>