【问题标题】:How to delay the Drag-Start to combine move and Drag-And-Drop dynamically如何延迟 Drag-Start 以动态结合移动和拖放
【发布时间】:2014-11-10 14:28:56
【问题描述】:

我想提供一个应用程序:

  • 允许四处移动图像(此处为矩形)
  • 如果该对象被移出工作区域,则启动拖放以转移到其他应用程序

因此,在对象在画布区域上移动期间 javafx DragDetected() 来得太快了,我抑制了 onDragDetected() 处理,并且在 onMouseDragged() 处理程序中,我尝试使用将 MouseDrag 事件转换为 Drag 事件

event.setDragDetect(true);

但是 onDragDetected() 再也不会出现.....我该怎么办?

完整的示例应用程序是:

package fx.samples;

import java.io.File;
import java.util.LinkedList;

import javax.imageio.ImageIO;

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class DragRectangle extends Application {
    Point2D lastXY = null;

    public void start(Stage primaryStage) {
        Pane mainPane = new Pane(); 
        Scene scene = new Scene(mainPane, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();

        Rectangle area = new Rectangle(0, 0, 500 , 500);

        Rectangle rect = new Rectangle(0, 0, 30, 30);
        rect.setFill(Color.RED);
        mainPane.getChildren().add(rect);

        rect.setOnMouseDragged(event -> {
            System.out.println("Move");
            Node on = (Node)event.getTarget();
            if (lastXY == null) {
                lastXY = new Point2D(event.getSceneX(), event.getSceneY());
            }
            double dx = event.getSceneX() - lastXY.getX();
            double dy = event.getSceneY() - lastXY.getY();
            on.setTranslateX(on.getTranslateX()+dx);
            on.setTranslateY(on.getTranslateY()+dy);
            lastXY = new Point2D(event.getSceneX(), event.getSceneY());
            if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) {
                System.out.println("->Drag");
                event.setDragDetect(true);
            } else {
                event.consume();
            }
        });

        rect.setOnDragDetected(event -> {
            System.out.println("Drag:"+event);
            if (area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) { event.consume(); return; }
            Node on = (Node)event.getTarget();
            Dragboard db = on.startDragAndDrop(TransferMode.COPY);
            db.setContent(makeClipboardContent(event, on, null));
            event.consume();
        });

        rect.setOnMouseReleased(d -> lastXY = null);
    }

    public static ClipboardContent makeClipboardContent(MouseEvent event, Node child, String text) {
        ClipboardContent cb = new ClipboardContent();
        if (text != null) {
            cb.put(DataFormat.PLAIN_TEXT, text);
        }
        if (!event.isShiftDown()) {
            SnapshotParameters params = new SnapshotParameters();
            params.setFill(Color.TRANSPARENT);
            Bounds b = child.getBoundsInParent();
            double f = 10;
            params.setViewport(new Rectangle2D(b.getMinX()-f, b.getMinY()-f, b.getWidth()+f+f, b.getHeight()+f+f));

            WritableImage image = child.snapshot(params, null);
            cb.put(DataFormat.IMAGE, image);

            try {
                File tmpFile = File.createTempFile("snapshot", ".png");
                LinkedList<File> list = new LinkedList<File>();
                ImageIO.write(SwingFXUtils.fromFXImage(image, null),
                        "png", tmpFile);
                list.add(tmpFile);
                cb.put(DataFormat.FILES, list);
            } catch (Exception e) {

            }
        }
        return cb;
    }

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

【问题讨论】:

    标签: drag-and-drop javafx


    【解决方案1】:

    好的,我花了几个小时阅读 JavaFX 源代码并玩弄 EventDispatcher 等...终于很容易了:

    简而言之:

    在 onMouseDragged() 处理程序中禁止系统拖动启动提议并代表您设置该标志:

    onMouseDragged(e -> {
        e.setDragDetect(false); // clear the system proposal
        if (...) e.setDragDetect(true); // trigger drag on your own decision
    }
    

    长文本:

    因此,启动 DragDetected 的机制是使用 MouseEvent MOUSE_DRAGGED。系统拖动检测会应用一些规则来判断当前鼠标拖动是否会被解释为拖动,这里是原始代码:

            if (dragDetected != DragDetectedState.NOT_YET) {
                mouseEvent.setDragDetect(false);
                return;
            }
    
            if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
                pressedX = mouseEvent.getSceneX();
                pressedY = mouseEvent.getSceneY();
    
                mouseEvent.setDragDetect(false);
    
            } else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) {
    
                double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX);
                double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY);
                mouseEvent.setDragDetect(deltaX > hysteresisSizeX ||
                                         deltaY > hysteresisSizeY);
    
            }
        }
    

    然后设置

    mouseEvent.setDragDetect(true) 
    

    在正常的 MOUSE_DRAG 事件中。该事件被向下传​​递并由所有“下游”EventDispatchers 处理...仅当该事件最终到达进行处理并且 isDragDetect 标志仍然为真时,才会生成后续 DragDetected 事件。

    所以我可以通过使用 EventDispatcher 清除 isDragDetect 标志来延迟 DragDetected:

            mainPane.setEventDispatcher((event, chain) -> {
                switch (event.getEventType().getName()) {
                    case "MOUSE_DRAGGED":
                        MouseEvent drag = (MouseEvent)event;
    
                        drag.setDragDetect(false);
                        if (!area.intersects(drag.getSceneX(), drag.getSceneY(), 1, 1)) {
                            System.out.println("->Drag down");
                            drag.setDragDetect(true);
                        }
                        break;
                }
    
                return chain.dispatchEvent(event);
            });
    

    如果此代码确定达到了拖动条件,它会简单地设置标志。

          drag.setDragDetect(true);
    

    现在我可以精确地移动我的对象并在它们移出应用程序区域时启动拖动。

    经过几分钟的思考:EventDispatcher 不是必需的,一切都可以在 onMouseDragged 处理程序中完成...

    完整代码:

    package fx.samples;
    
    import java.io.File;
    import java.util.LinkedList;
    
    import javafx.application.Application;
    import javafx.embed.swing.SwingFXUtils;
    import javafx.geometry.Bounds;
    import javafx.geometry.Point2D;
    import javafx.geometry.Rectangle2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.ClipboardContent;
    import javafx.scene.input.DataFormat;
    import javafx.scene.input.Dragboard;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.input.TransferMode;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.stage.Stage;
    
    import javax.imageio.ImageIO;
    
    
    public class DragRectangle extends Application {
        Point2D lastXY = null;
    
        public void start(Stage primaryStage) {
            Pane mainPane = new Pane(); 
            Scene scene = new Scene(mainPane, 500, 500);
            primaryStage.setScene(scene);
            primaryStage.show();
    
            Rectangle area = new Rectangle(0, 0, 500 , 500);
    
            Rectangle rect = new Rectangle(0, 0, 30, 30);
            rect.setFill(Color.RED);
            mainPane.getChildren().add(rect);
    
            rect.setOnMouseDragged(event -> {
                System.out.println("Move");
                event.setDragDetect(false);
                Node on = (Node)event.getTarget();
                if (lastXY == null) {
                    lastXY = new Point2D(event.getSceneX(), event.getSceneY());
                }
                double dx = event.getSceneX() - lastXY.getX();
                double dy = event.getSceneY() - lastXY.getY();
                on.setTranslateX(on.getTranslateX()+dx);
                on.setTranslateY(on.getTranslateY()+dy);
                lastXY = new Point2D(event.getSceneX(), event.getSceneY());
                if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) event.setDragDetect(true);
                event.consume();
            });
    
            rect.setOnDragDetected(event -> {
                System.out.println("Drag:"+event);
                Node on = (Node)event.getTarget();
                Dragboard db = on.startDragAndDrop(TransferMode.COPY);
                db.setContent(makeClipboardContent(event, on, "red rectangle"));
                event.consume();
            });
    
            rect.setOnMouseReleased(d ->  lastXY = null);
        }
    
        public static ClipboardContent makeClipboardContent(MouseEvent event, Node child, String text) {
            ClipboardContent cb = new ClipboardContent();
            if (text != null) {
                cb.put(DataFormat.PLAIN_TEXT, text);
            }
            if (!event.isShiftDown()) {
                SnapshotParameters params = new SnapshotParameters();
                params.setFill(Color.TRANSPARENT);
                Bounds b = child.getBoundsInParent();
                double f = 10;
                params.setViewport(new Rectangle2D(b.getMinX()-f, b.getMinY()-f, b.getWidth()+f+f, b.getHeight()+f+f));
    
                WritableImage image = child.snapshot(params, null);
                cb.put(DataFormat.IMAGE, image);
    
                try {
                    File tmpFile = File.createTempFile("snapshot", ".png");
                    LinkedList<File> list = new LinkedList<File>();
                    ImageIO.write(SwingFXUtils.fromFXImage(image, null),
                            "png", tmpFile);
                    list.add(tmpFile);
                    cb.put(DataFormat.FILES, list);
                } catch (Exception e) {
    
                }
            }
            return cb;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

    • 事件调度器工作得很好。毕竟你是对的,你只需要将检测到的拖动标志保持为假,直到它为真。顺便说一句,您可以考虑这个:db.setDragView(image, event.getX(), event.getY());,以允许鼠标拖动的节点图像与拖动的图像之间的平滑过渡。请注意,您不能从中使用场景坐标。
    【解决方案2】:

    基于this:

    默认的拖动检测机制使用按下按钮的鼠标移动和迟滞相结合。应用程序可以增强此行为。每个 MOUSE_PRESSED 和 MOUSE_DRAGGED 事件都有一个 dragDetect 标志,用于确定是否检测到拖动手势。此标志的默认值取决于默认检测机制,可以通过在事件处理程序内部调用 setDragDetect() 来修改。当这些事件之一的处理结束且 dragDetect 标志设置为 true 时,将 DRAG_DETECTED MouseEvent 发送到潜在的手势源(按下鼠标按钮的对象)。此事件通知手势检测。

    您假设在鼠标拖动开始后在某个时间点启用setDragDetect(true)会触发拖动事件,但这是行不通的。

    原因是有一些私有DragDetectedState标志,用于在手势开始时设置拖动状态。并且一旦被选中,就无法更改。

    所以如果事件没有触发,你可以自己手动触发:

    if (!area.intersects(event.getSceneX(), event.getSceneY(), 1, 1)) {
            System.out.println("->Drag");
            event.setDragDetect(true);
            Event.fireEvent(rect, new MouseEvent(MouseEvent.DRAG_DETECTED, 0, 0, 0, 0, MouseButton.PRIMARY, 0, false, false, false, false, true, false, false, true, true, true, null));
        }
    

    这将有效地触发检测到拖动的事件,并且将调用rect.setOnDragDetected()

    但这就是你会得到的:

    Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException: 
         Cannot start drag and drop outside of DRAG_DETECTED event handler
    at javafx.scene.Scene.startDragAndDrop(Scene.java:5731)
    at javafx.scene.Node.startDragAndDrop(Node.java:2187)
    

    基本上,您不能将 DragDetected 与 MouseDragged 结合使用,因为必须在 Drag_Detected event 中调用 startDragAndDrop 方法:

    确认在此节点上识别的潜在拖放手势。只能从 DRAG_DETECTED 事件处理程序中调用。

    因此,与其尝试组合两个不同的事件,我的建议是您只需使用检测到拖动的事件,允许您的场景或其部分接受拖放,并在那里翻译您的节点,同时,如果拖放离开场景,你可以把文件放到新的目标上。

    类似这样的:

    @Override
    public void start(Stage primaryStage) {
        Pane mainPane = new Pane();
    
        Rectangle rect = new Rectangle(0, 0, 30, 30);
        rect.setFill(Color.RED);
        mainPane.getChildren().add(rect);
    
        Scene scene = new Scene(mainPane, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
    
        rect.setOnDragDetected(event -> {
            Node on = (Node)event.getSource();
            Dragboard db = on.startDragAndDrop(TransferMode.ANY);
            db.setContent(makeClipboardContent(event, on, null));
            event.consume();
        });
    
        mainPane.setOnDragOver(e->{
            e.acceptTransferModes(TransferMode.ANY);
        });
    
        mainPane.setOnDragExited(e->{
            rect.setLayoutX(e.getSceneX()-rect.getWidth()/2d);
            rect.setLayoutY(e.getSceneY()-rect.getHeight()/2d);
        });
    }
    

    【讨论】:

    • “你假设只在某个时候启用 setDragDetect(true) 会触发拖动事件,这是行不通的。” -> 如果我在由于滞后而开始拖动之前这样做(如果我在第一个 Move 事件中这样做),它会起作用......所以我希望它以后也能起作用。
    • 我已经尝试使用 fireEvent(),结果您还指出:异常“无法在 DRAG_DETECTED 事件处理程序之外开始拖放”...(多么可悲)
    • 我首先在我的应用程序内部实现了拖放,但这提供了非常糟糕的用户反馈,因为对象的拖动图标立即居中,并且如果图像更大,它将按比例缩小显示。因此,用户不知道下落的确切位置。所以我决定只有在移动过程中离开应用程序时才开始拖动。感谢您的帮助,但我仍然需要解决此问题。
    • @Jens-PeterHaack 关于第一条评论,我已经编辑了我的答案以澄清。 “在某个时候”我的意思是“在鼠标拖动开始后的某个时候”,并不是说它根本不起作用。
    • @Jens-PeterHaack 关于不好的反馈,你可以使用这个:db.setDragView(image, event.getSceneX(), event.getSceneY());,其中image已经在makeClipboardContent上创建,(offsetX, offsetY)是位置(x ,y) 的光标在图像上。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 1970-01-01
    • 1970-01-01
    • 2011-01-19
    • 2016-03-10
    • 2019-12-03
    • 2015-08-31
    相关资源
    最近更新 更多