javafx - 实现拖动多个选中的节点

标签 javafx

  • 我有一个扩展 javafx.scene.Node 的类,比如 DraggableNode。
  • 我已经为任何此类 DraggableNode 编写了拖动事件处理程序。

    Class DraggableNode extends Node
    {
        ...
         onMouseDraggedProperty().set(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {
                   offsetX = dragStartPoitionX - event.getSceneX();
                   offsetY = dragStartPoitionY - event.getSceneY();
                   setLayoutX (event.getSceneX());
                   setLayoutY (event.getSceneY());
                     ...
                }
         }
     }
    
  • 此事件处理程序可以很好地单独拖动此节点。
  • 接下来,我需要选择多个这样的“节点”,并且拖动所选节点之一应该通过“offsetX”更改所有所选节点的 (x,y) 坐标& "offsetY"
  • 还实现了选择算法(在扩展 Pane 的类中,其中这些节点被添加为 children)。但是,我需要以某种方式触发其他选定节点的拖动事件处理程序,以便最终输出看起来像多次拖动。

最佳答案

首先您创建一个选择模型,比如一个 Set。每当您将一个节点添加到您的选择中时,您就将它添加到选择模型中。

当您拖动一个节点时,您也只需更改事件处理程序中选择模型的所有其他节点的位置。

就这么简单。

下面的代码还支持橡皮筋选择、选择期间的 shift 和 ctrl 按键等:

NodeSelection.java

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;

public class NodeSelection extends Application {

    public static Image image = new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg");
//  public Image image = new Image( getClass().getResource( "tiger.jpg").toExternalForm());

    SelectionModel selectionModel = new SelectionModel();

    DragMouseGestures dragMouseGestures = new DragMouseGestures();

    static Random rnd = new Random();

    @Override
    public void start(Stage primaryStage) {

        Pane pane = new Pane();
        pane.setStyle("-fx-background-color:white");

        new RubberBandSelection( pane);

        double width = 200;
        double height = 160;

        double padding = 20;
        for( int row=0; row < 4; row++) {
            for( int col=0; col < 4; col++) {

                Selectable selectable = new Selectable( width, height);
                selectable.relocate( padding * (col+1) + width * col, padding * (row + 1) + height * row);

                pane.getChildren().add(selectable);

                dragMouseGestures.makeDraggable(selectable);

            }
        }

        Label infoLabel = new Label( "Drag on scene for Rubberband Selection. Shift+Click to add to selection, CTRL+Click to toggle selection. Drag selected nodes for multi-dragging.");
        pane.getChildren().add( infoLabel);

        Scene scene = new Scene( pane, 1600, 900);
        scene.getStylesheets().add( getClass().getResource("application.css").toExternalForm());

        primaryStage.setScene( scene);
        primaryStage.show();        



    }

    private class Selectable extends Region {

        ImageView view;

        public Selectable( double width, double height) {

            view = new ImageView( image);
            view.setFitWidth(width);
            view.setFitHeight(height);

            getChildren().add( view);

            this.setPrefSize(width, height);
        }

    }

    private class SelectionModel {

        Set<Node> selection = new HashSet<>();

        public void add( Node node) {

            if( !node.getStyleClass().contains("highlight")) {
                node.getStyleClass().add( "highlight");
            }

            selection.add( node);
        }

        public void remove( Node node) {
            node.getStyleClass().remove( "highlight");
            selection.remove( node);
        }

        public void clear() {

            while( !selection.isEmpty()) {
                remove( selection.iterator().next());
            }

        }

        public boolean contains( Node node) {
            return selection.contains(node);
        }

        public int size() {
            return selection.size();
        }

        public void log() {
            System.out.println( "Items in model: " + Arrays.asList( selection.toArray()));
        }

    }

    private class DragMouseGestures {

        final DragContext dragContext = new DragContext();

        private boolean enabled = false;

        public void makeDraggable(final Node node) {

            node.setOnMousePressed(onMousePressedEventHandler);
            node.setOnMouseDragged(onMouseDraggedEventHandler);
            node.setOnMouseReleased(onMouseReleasedEventHandler);

        }

        EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                // don't do anything if the user is in the process of adding to the selection model
                if( event.isControlDown() || event.isShiftDown())
                    return;

                Node node = (Node) event.getSource();

                dragContext.x = node.getTranslateX() - event.getSceneX();
                dragContext.y = node.getTranslateY() - event.getSceneY();

                // clear the model if the current node isn't in the selection => new selection
                if( !selectionModel.contains(node)) {
                    selectionModel.clear();
                    selectionModel.add( node);
                }

                // flag that the mouse released handler should consume the event, so it won't bubble up to the pane which has a rubberband selection mouse released handler
                enabled = true;

                // prevent rubberband selection handler 
                event.consume();
            }
        };

        EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                if( !enabled)
                    return;

                // all in selection
                for( Node node: selectionModel.selection) {
                    node.setTranslateX( dragContext.x + event.getSceneX());
                    node.setTranslateY( dragContext.y + event.getSceneY());
                }

            }
        };

        EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                // prevent rubberband selection handler 
                if( enabled) {

                    // set node's layout position to current position,remove translate coordinates
                    for( Node node: selectionModel.selection) {
                        fixPosition(node);
                    }

                    enabled = false;

                    event.consume();
                }
            }
        };

        /**
         * Set node's layout position to current position, remove translate coordinates.
         * @param node
         */
        private void fixPosition( Node node) {

            double x = node.getTranslateX();
            double y = node.getTranslateY();

            node.relocate(node.getLayoutX() + x, node.getLayoutY() + y);

            node.setTranslateX(0);
            node.setTranslateY(0);

        }

        class DragContext {

            double x;
            double y;

        }

    }

    private class RubberBandSelection {

        final DragContext dragContext = new DragContext();
        Rectangle rect;

        Pane group;
        boolean enabled = false;

        public RubberBandSelection( Pane group) {

            this.group = group;

            rect = new Rectangle( 0,0,0,0);
            rect.setStroke(Color.BLUE);
            rect.setStrokeWidth(1);
            rect.setStrokeLineCap(StrokeLineCap.ROUND);
            rect.setFill(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.6));

            group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
            group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
            group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);

        }

        EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                // simple flag to prevent multiple handling of this event or we'd get an exception because rect is already on the scene
                // eg if you drag with left mouse button and while doing that click the right mouse button
                if( enabled)
                    return;

                dragContext.mouseAnchorX = event.getSceneX();
                dragContext.mouseAnchorY = event.getSceneY();

                rect.setX(dragContext.mouseAnchorX);
                rect.setY(dragContext.mouseAnchorY);
                rect.setWidth(0);
                rect.setHeight(0);

                group.getChildren().add( rect);

                event.consume();

                enabled = true;
            }
        };

        EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                if( !event.isShiftDown() && !event.isControlDown()) {
                    selectionModel.clear();
                }

                for( Node node: group.getChildren()) {

                    if( node instanceof Selectable) {
                        if( node.getBoundsInParent().intersects( rect.getBoundsInParent())) {

                            if( event.isShiftDown()) {

                                selectionModel.add( node);

                            } else if( event.isControlDown()) {

                                if( selectionModel.contains( node)) {
                                    selectionModel.remove( node);
                                } else {
                                    selectionModel.add( node);
                                }
                            } else {
                                selectionModel.add( node);
                            }

                        }
                    }

                }

                selectionModel.log();

                rect.setX(0);
                rect.setY(0);
                rect.setWidth(0);
                rect.setHeight(0);

                group.getChildren().remove( rect);

                event.consume();

                enabled = false;
            }
        };

        EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {

                double offsetX = event.getSceneX() - dragContext.mouseAnchorX;
                double offsetY = event.getSceneY() - dragContext.mouseAnchorY;

                if( offsetX > 0)
                    rect.setWidth( offsetX);
                else {
                    rect.setX(event.getSceneX());
                    rect.setWidth(dragContext.mouseAnchorX - rect.getX());
                }

                if( offsetY > 0) {
                    rect.setHeight( offsetY);
                } else {
                    rect.setY(event.getSceneY());
                    rect.setHeight(dragContext.mouseAnchorY - rect.getY());
                }

                event.consume();

            }
        };

        private final class DragContext {

            public double mouseAnchorX;
            public double mouseAnchorY;


        }
    }


    public static void main(String[] args) {
        launch(args);
    }

}

application.css

.highlight {
    -fx-effect: dropshadow(three-pass-box, red, 4, 4, 0, 0);
}

截图:

enter image description here

关于javafx - 实现拖动多个选中的节点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30481363/

相关文章:

JavaFx 离线能力?

java - 在 CSS (JavaFX) 中为 TableView 设置样式

java - 如何提高 JavaFX 中场景屏幕截图生成的图像的 DPI 质量

java - 如何检测嵌套分割 Pane 分隔线上的鼠标单击事件?

JavaFX - 如何检测 Windows 注销/关闭请求?

JavaFX:为什么当有 setter 时 Window.xProperty() 和 Window.yProperty() 是只读的?

JavaFX - TreeTableView sortPolicyProperty()

java - VSCode 错误 : JavaFX runtime components are missing, 并且需要运行此应用程序

image - JavaFx 图像大小调整

java - 如何使用相同的模型对象初始化 JavaFX Controller ?