javauser-interfacejavafxmouseeventmouse

MouseDragged detection for multiple nodes while holding the button JavaFX


How could I implement a system for my application that lets me color these rectangles showed below, while holding left mouse button? And when released it stops coloring. I searched trough the internet but I still can't understand how do those MouseEvents work.

Rectangles to color


Solution

  • From the documentation of javafx.scene.input.MouseEvent:

    Dragging gestures

    There are three types of dragging gestures. They are all initiated by a mouse press event and terminated as a result of a mouse released event, the source node decides which gesture will take place.

    The simple press-drag-release gesture is default. It's best used to allow changing size of a shape, dragging it around and so on. Whole press-drag-release gesture is delivered to one node. When mouse button is pressed, the top-most node is picked and all subsequent mouse events are delivered to the same node until the button is released. If a mouse clicked event is generated from these events, it is still delivered to the same node.

    During simple press-drag-release gesture, the other nodes are not involved and don't get any events. If these nodes need to be involved in the gesture, full press-drag-release gesture has to be activated. This gesture is best used for connecting nodes by "wires", dragging nodes to other nodes etc. This gesture type is more closely described at MouseDragEvent which contains the events delivered to the gesture targets.

    The third gesture type is platform-supported drag-and-drop gesture. It serves best to transfer data and works also between (not necessarily FX) applications. This gesture type is more closely described at DragEvent.

    In a short summary, simple press-drag-release gesture is activated automatically when a mouse button is pressed and delivers all MouseEvents to the gesture source. When you start dragging, eventually the DRAG_DETECTED event arrives. In its handler you can either start full press-drag-release gesture by calling startFullDrag method on a node or scene - the MouseDragEvents start to be delivered to gesture targets, or you can start drag and drop gesture by calling startDragAndDrop method on a node or scene - the system switches into the drag and drop mode and DragEvents start to be delivered instead of MouseEvents. If you don't call any of those methods, the simple press-drag-release gesture continues.

    [...]

    If I understand your question correctly, you want to be able to drag the mouse over multiple nodes and have them react, all in one gesture. You'll want to use a full press-drag-release gesture to accomplish this. As documented, you have to listen for a DRAG_DETECTED event and call Node#startFullDrag() or Scene#startFullDrag() to activate the full press-drag-release gesture. Then each "square" in your UI needs to listen for MOUSE_DRAG_ENTERED events. Notice that the event type is MOUSE_DRAG_ENTERED and not MOUSE_ENTERED.

    Here's an example:

    import javafx.application.Application;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.layout.GridPane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.stage.Stage;
    
    public class App extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        GridPane root = new GridPane();
        root.setPadding(new Insets(2));
        root.setVgap(2);
        root.setHgap(2);
    
        // start full press-drag-release gesture
        root.setOnDragDetected(
            event -> {
              if (event.getButton() == MouseButton.PRIMARY) {
                event.consume();
                root.startFullDrag();
              }
            });
    
        for (int i = 0; i < 12; i++) {
          for (int j = 0; j < 12; j++) {
            Rectangle rect = new Rectangle(50, 50, Color.WHITE);
            rect.setStroke(Color.BLACK);
            root.add(rect, i, j);
    
            // detect MOUSE_DRAG_ENTERED events
            rect.setOnMouseDragEntered(
                event -> {
                  event.consume();
                  rect.setFill(Color.BLACK);
                });
          }
        }
    
        primaryStage.setTitle("MouseDragEvent Example");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
      }
    }
    

    The above listens for DRAG_DETECTED events by setting the Node#onDragDetected property on the root GridPane. Note that if you start dragging on one of the Rectangles then the event will bubble up to the root and be handled by the aforementioned handler. Also, since you explicitly mention the left mouse button I added a check for if the mouse button is the primary or not.

    Then each Rectangle listens for MOUSE_DRAG_ENTERED events by having their Node#onMouseDragEntered property set. These events will only be delivered if a full press-drag-release gesture is in effect.