在JavaFX中显示快照的vbox元素拖放

3

我想将一个元素作为父元素拖放到vbox上,并在拖放过程中显示节点移动,如何在最小的更改下完成此操作。


你能再解释一下吗?你是想从一个控件拖动数据到另一个控件(例如从一个ListView拖动项目到另一个ListView),还是只想通过拖动来重新定位节点? - James_D
不要只是从vbox中拖动节点并插入到vbox的不同行中。 - Amir Mousavi
3个回答

6
只需在VBox元素上注册鼠标监听器。在dragDetected事件中,您需要调用节点上的startFullDrag()方法,在dragReleased事件中旋转VBox的子节点。如果您想向用户提供有关拖放的视觉提示,则可以使用dragEntered和dragExited事件。

有关更多信息,请参见API文档

简单示例(在JavaFX 8中代码更加简洁):

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            final VBox root = new VBox(5);
            final ScrollPane scroller = new ScrollPane();
            scroller.setContent(root);
            final Scene scene = new Scene(scroller,400,200);

            for (int i=1; i<=20; i++) {
                final Label label = new Label("Item "+i);
                addWithDragging(root, label);
            }

            // in case user drops node in blank space in root:
            root.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
                @Override
                public void handle(MouseDragEvent event) {
                    int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
                    rotateNodes(root, indexOfDraggingNode, root.getChildren().size()-1);
                }
            });

            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    private void addWithDragging(final VBox root, final Label label) {
        label.setOnDragDetected(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                label.startFullDrag();
            }
        });

        // next two handlers just an idea how to show the drop target visually:
        label.setOnMouseDragEntered(new EventHandler<MouseDragEvent>() {
            @Override
            public void handle(MouseDragEvent event) {
                label.setStyle("-fx-background-color: #ffffa0;");
            }
        });
        label.setOnMouseDragExited(new EventHandler<MouseDragEvent>() {
            @Override
            public void handle(MouseDragEvent event) {
                label.setStyle("");
            }
        });

        label.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
            @Override
            public void handle(MouseDragEvent event) {
                label.setStyle("");
                int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
                int indexOfDropTarget = root.getChildren().indexOf(label);
                rotateNodes(root, indexOfDraggingNode, indexOfDropTarget);
                event.consume();
            }
        });
        root.getChildren().add(label);
    }

    private void rotateNodes(final VBox root, final int indexOfDraggingNode,
            final int indexOfDropTarget) {
        if (indexOfDraggingNode >= 0 && indexOfDropTarget >= 0) {
            final Node node = root.getChildren().remove(indexOfDraggingNode);
            root.getChildren().add(indexOfDropTarget, node);
        }
    }


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

很好,谢谢,但我想在拖放过程中显示节点快照和鼠标移动,就像在场景构建器中移动节点一样,但只在vbox中。 - Amir Mousavi
这需要更多的工作,但并不太难。在dragDetected监听器中,通过获取拖动节点的快照来创建一个图像。从该图像创建一个ImageView,并在其上调用setManaged(false),然后将其添加到VBox中。为VBox注册一个MouseListener,在鼠标拖动时调用relocate(...)方法来移动ImageView。在dragReleased中,从VBox中删除ImageView和Listener。 - James_D
另外,在Java 8中,如果您启动完整的拖放(而不是上面示例中的“完整按下-拖动-释放”),则可以在Dragboard上设置拖动视图。这可能更容易,但感觉完整的拖放并不真正适合您只是移动节点而不是在应用程序中传输数据。 - James_D
非常好的答案。这应该被标记为答案。同时,我会将@James_D的建议发布在评论中作为答案 - 这显示了如何显示可拖动节点的快照。 - sinujohn
当MouseDragEvent完成并释放了用于拖动的鼠标按钮后,我该如何进行一些清理工作?我的意思是,如果我在root.setOnMouseDragReleased中执行它,那么它只会在释放发生在应用程序阈值内时才被应用。 - Arceus

3

这是对@James_D优秀答案的补充说明。

这展示了如何按照@James_D在评论中建议的方式,向可拖动节点添加图像预览:

     private void addPreview(final VBox root, final Label label) {
        ImageView imageView = new ImageView(label.snapshot(null, null));
        imageView.setManaged(false);
        imageView.setMouseTransparent(true);
        root.getChildren().add(imageView);
        root.setUserData(imageView);
        root.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                imageView.relocate(event.getX(), event.getY());
            }
        });
    }

    private void removePreview(final VBox root) {
        root.setOnMouseDragged(null);
        root.getChildren().remove(root.getUserData());
        root.setUserData(null);
    }

label.setOnDragDetected()中调用addPreview()。在label.setOnMouseDragReleased()root.setOnMouseDragReleased()中调用removePreview()


2
现在有一个更好的解决方案,更加简洁明了。
// Root is the node you want to drag, not the scene root.
root.setOnDragDetected(mouseEvent -> {
    final ImageView preview = new ImageView(root.snapshot(null, null));

    final Dragboard db = root.startDragAndDrop(TransferMode.ANY);
    db.setContent( // Set your content to something here.
    );
    db.setDragView(preview.getImage());

    mouseEvent.consume();
});

这与@james_d给出的答案完全不同,而且根本没有涉及在VBoxes中拖放子项。 - nightpool

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接