60

我想知道是否可以检测到 JavaFX 2 中的双击?如何 ?

我想在单击和双击之间制作不同的事件。

谢谢

4

11 回答 11

102

是的,您可以检测单次、双次甚至多次点击:

myNode.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent mouseEvent) {
        if(mouseEvent.getButton().equals(MouseButton.PRIMARY)){
            if(mouseEvent.getClickCount() == 2){
                System.out.println("Double clicked");
            }
        }
    }
});

MouseButton.PRIMARY用于确定是否触发了鼠标左键(通常)事件。阅读 apigetClickCount()得出的结论是,除了单次或双次之外,可能还有多次点击计数。但是我发现很难区分单击事件和双击事件。因为双击的第一次点击计数也会上升一个事件。

于 2012-06-08T14:30:33.903 回答
7

这是另一段代码,如果您必须区分单击和双击,并且在任何一种情况下都必须采取特定操作,则可以使用该代码。

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class DoubleClickDetectionTest extends Application {

    boolean dragFlag = false;

    int clickCounter = 0;

    ScheduledThreadPoolExecutor executor;

    ScheduledFuture<?> scheduledFuture;

    public DoubleClickDetectionTest() {
        executor = new ScheduledThreadPoolExecutor(1);
        executor.setRemoveOnCancelPolicy(true);
    }

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        StackPane root = new StackPane();

        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();

        root.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent e) {
                if (e.getButton().equals(MouseButton.PRIMARY)) {
                    dragFlag = true;
                }
            }
        });

        root.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent e) {
                if (e.getButton().equals(MouseButton.PRIMARY)) {
                    if (!dragFlag) {
                        System.out.println(++clickCounter + " " + e.getClickCount());
                        if (e.getClickCount() == 1) {
                            scheduledFuture = executor.schedule(() -> singleClickAction(), 500, TimeUnit.MILLISECONDS);
                        } else if (e.getClickCount() > 1) {
                            if (scheduledFuture != null && !scheduledFuture.isCancelled() && !scheduledFuture.isDone()) {
                                scheduledFuture.cancel(false);
                                doubleClickAction();
                            }
                        }
                    }
                    dragFlag = false;
                }
            }
        });
    }

    @Override
    public void stop() {
        executor.shutdown();
    }

    private void singleClickAction() {
        System.out.println("Single-click action executed.");
    }

    private void doubleClickAction() {
        System.out.println("Double-click action executed.");
    }
}
于 2016-03-27T09:26:31.997 回答
4

遵循 Java SE 8 lambda 表达式将如下所示:

node.setOnMouseClicked(event -> {
    if(event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
        handleSomeAction();
    }
});

一旦你习惯了 lambda 表达式 - 它们最终会比原始类实例化和覆盖 (x) 方法更容易理解。-在我看来-

于 2018-03-08T21:14:50.837 回答
3

P. Pandey 的回应是实际上区分单击和双击的最简单方法,但它对我不起作用。一方面,函数“currentTimeMillis”已经返回毫秒,所以似乎没有必要将它除以 1000。下面的版本以更一致的方式为我工作。

 @Override
 public void handle(MouseEvent t) {

        long diff = 0;

        currentTime=System.currentTimeMillis();

        if(lastTime!=0 && currentTime!=0){
            diff=currentTime-lastTime;

            if( diff<=215)
                isdblClicked=true;
            else
                isdblClicked=false;
        }

        lastTime=currentTime;

        System.out.println("IsDblClicked()"+isdblClicked); 

       //use the isdblClicked flag...   
}
于 2016-07-18T03:18:40.693 回答
2

这是我实现双击的方式

if (e.getEventType().equals(MouseEvent.MOUSE_CLICKED) && !drag_Flag) {
                long diff = 0;
            if(time1==0)
             time1=System.currentTimeMillis();
            else
            time2=System.currentTimeMillis();
            if(time1!=0 && time2!=0)
            diff=time2-time1;
            if((diff/1000)<=215 && diff>0)
            {
                isdblClicked=true;
            }
            else
            {
                isdblClicked=false;
            }

            System.out.println("IsDblClicked()"+isdblClicked); 

}

于 2016-02-12T12:03:38.887 回答
2

由于默认无法区分单击和双击,所以我们采用如下方式:

单击时,我们将单击操作包装在可中止的可运行文件中。SINGLE_CLICK_DELAY这个runnable在被执行之前会等待一定的时间(即, )。

同时,如果发生第二次点击,即双击,则单击操作中止,仅执行双击操作。

这样,要么执行单击操作,要么执行双击操作,但从不执行两者。


以下是完整代码。要使用它,只需要将TODO三行替换为所需的处理程序。

private static final int SINGLE_CLICK_DELAY = 250;
private ClickRunner latestClickRunner = null;

private class ClickRunner implements Runnable {

    private final Runnable onSingleClick;
    private boolean aborted = false;

    public ClickRunner(Runnable onSingleClick) {
        this.onSingleClick = onSingleClick;
    }

    public void abort() {
        this.aborted = true;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(SINGLE_CLICK_DELAY);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!aborted) {
            System.out.println("Execute Single Click");
            Platform.runLater(() -> onSingleClick.run());
        }
    }
}

private void init() {
    container.setOnMouseClicked(me -> {
        switch (me.getButton()) {
            case PRIMARY:
                if (me.getClickCount() == 1) {
                    System.out.println("Single Click");
                    latestClickRunner = new ClickRunner(() -> {
                      // TODO: Single-left-click operation
                    });
                    CompletableFuture.runAsync(latestClickRunner);
                }
                if (me.getClickCount() == 2) {
                    System.out.println("Double Click");
                    if (latestClickRunner != null) {
                        System.out.println("-> Abort Single Click");
                        latestClickRunner.abort();
                    }
                    // TODO: Double-left-click operation
                }
                break;
            case SECONDARY:
                // TODO: Right-click operation
                break;
            default:
                break;
        }
    });
}
于 2018-03-21T09:38:45.967 回答
2

不确定是否有人仍然遵循此 OP 或参考它,但以下是我区分单击和双击的版本。尽管大多数答案都是可以接受的,但如果可以以适当的可重复方式完成,那将非常有用。

我遇到的挑战之一是需要在多个位置的多个节点上进行单击差异化。我无法在每个节点上执行相同的重复繁琐逻辑。它应该以通用方式完成。

所以我选择实现一个自定义 EventDispatcher 并在节点级别使用这个调度程序,或者我可以将它直接应用到 Scene 以使其适用于所有子节点。

为此,我创建了一个新的 MouseEvent,即“MOUSE_DOUBLE_CLICKED”,所以我仍然坚持标准的 JavaFX 实践。现在我可以像其他鼠标事件类型一样包含双击事件过滤器/处理程序。

node.addEventFilter(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e->{..<code to handle double_click>..});
node.addEventHandler(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e->{..<code to handle double_click>..});

下面是这个自定义事件调度器的实现和完整的工作演示。

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class DoubleClickEventDispatcherDemo extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Rectangle box1 = new Rectangle(150, 150);
        box1.setStyle("-fx-fill:red;-fx-stroke-width:2px;-fx-stroke:black;");
        addEventHandlers(box1, "Red Box");

        Rectangle box2 = new Rectangle(150, 150);
        box2.setStyle("-fx-fill:yellow;-fx-stroke-width:2px;-fx-stroke:black;");
        addEventHandlers(box2, "Yellow Box");

        HBox pane = new HBox(box1, box2);
        pane.setSpacing(10);
        pane.setAlignment(Pos.CENTER);
        addEventHandlers(pane, "HBox");

        Scene scene = new Scene(new StackPane(pane), 450, 300);
        stage.setScene(scene);
        stage.show();

        // SETTING CUSTOM EVENT DISPATCHER TO SCENE
        scene.setEventDispatcher(new DoubleClickEventDispatcher(scene.getEventDispatcher()));
    }

    private void addEventHandlers(Node node, String nodeId) {
        node.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> System.out.println("" + nodeId + " mouse clicked filter"));
        node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("" + nodeId + " mouse clicked handler"));

        node.addEventFilter(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e -> System.out.println("" + nodeId + " mouse double clicked filter"));
        node.addEventHandler(CustomMouseEvent.MOUSE_DOUBLE_CLICKED, e -> System.out.println(nodeId + " mouse double clicked handler"));
    }

    /**
     * Custom MouseEvent
     */
    interface CustomMouseEvent {
        EventType<MouseEvent> MOUSE_DOUBLE_CLICKED = new EventType<>(MouseEvent.ANY, "MOUSE_DBL_CLICKED");
    }

    /**
     * Custom EventDispatcher to differentiate from single click with double click.
     */
    class DoubleClickEventDispatcher implements EventDispatcher {

        /**
         * Default delay to fire a double click event in milliseconds.
         */
        private static final long DEFAULT_DOUBLE_CLICK_DELAY = 215;

        /**
         * Default event dispatcher of a node.
         */
        private final EventDispatcher defaultEventDispatcher;

        /**
         * Timeline for dispatching mouse clicked event.
         */
        private Timeline clickedTimeline;

        /**
         * Constructor.
         *
         * @param initial Default event dispatcher of a node
         */
        public DoubleClickEventDispatcher(final EventDispatcher initial) {
            defaultEventDispatcher = initial;
        }

        @Override
        public Event dispatchEvent(final Event event, final EventDispatchChain tail) {
            final EventType<? extends Event> type = event.getEventType();
            if (type == MouseEvent.MOUSE_CLICKED) {
                final MouseEvent mouseEvent = (MouseEvent) event;
                final EventTarget eventTarget = event.getTarget();
                if (mouseEvent.getClickCount() > 1) {
                    if (clickedTimeline != null) {
                        clickedTimeline.stop();
                        clickedTimeline = null;
                        final MouseEvent dblClickedEvent = copy(mouseEvent, CustomMouseEvent.MOUSE_DOUBLE_CLICKED);
                        Event.fireEvent(eventTarget, dblClickedEvent);
                    }
                    return mouseEvent;
                }
                if (clickedTimeline == null) {
                    final MouseEvent clickedEvent = copy(mouseEvent, mouseEvent.getEventType());
                    clickedTimeline = new Timeline(new KeyFrame(Duration.millis(DEFAULT_DOUBLE_CLICK_DELAY), e -> {
                        Event.fireEvent(eventTarget, clickedEvent);
                        clickedTimeline = null;
                    }));
                    clickedTimeline.play();
                    return mouseEvent;
                }
            }
            return defaultEventDispatcher.dispatchEvent(event, tail);
        }

        /**
         * Creates a copy of the provided mouse event type with the mouse event.
         *
         * @param e         MouseEvent
         * @param eventType Event type that need to be created
         * @return New mouse event instance
         */
        private MouseEvent copy(final MouseEvent e, final EventType<? extends MouseEvent> eventType) {
            return new MouseEvent(eventType, e.getSceneX(), e.getSceneY(), e.getScreenX(), e.getScreenY(),
                    e.getButton(), e.getClickCount(), e.isShiftDown(), e.isControlDown(), e.isAltDown(),
                    e.isMetaDown(), e.isPrimaryButtonDown(), e.isMiddleButtonDown(),
                    e.isSecondaryButtonDown(), e.isSynthesized(), e.isPopupTrigger(),
                    e.isStillSincePress(), e.getPickResult());
        }
    }
}
于 2019-10-30T00:34:28.860 回答
0

使用 PauseTransition 的解决方案:

PauseTransition singlePressPause = new PauseTransition(Duration.millis(500));
singlePressPause.setOnFinished(e -> {
    // single press
});

node.setOnMousePressed(e -> {

    if (e.isPrimaryButtonDown() && e.getClickCount() == 1) {
        singlePressPause.play();
    }

    if (e.isPrimaryButtonDown() && e.getClickCount() == 2) {
        singlePressPause.stop();
        // double press
    }
});

于 2019-10-20T11:02:45.767 回答
0

我正在使用的单击与双击的替代方法是单击与按住(大约四分之一到半秒左右),然后释放按钮。该技术可以使用线程可中止计时器,如上面的一些代码片段中的那样,以区分两者。假设实际的事件处理发生在按钮释放时,这种替代方法的优点是单击可以正常工作(即,没有任何延迟),并且对于按住,您可以在按钮被释放时给用户一些视觉反馈持有足够长的时间以被释放(因此对于执行了哪个操作永远不会有任何歧义)。

于 2020-01-05T15:16:17.770 回答
0

如果您正在测试按下了多少个鼠标按钮(==2),请不要在子方法中对其进行编码!接下来是工作:

listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                if( mouseEvent.getButton().equals(MouseButton.SECONDARY)) {
                    System.out.println("isSecondaryButtonDown");
                    mouseEvent.consume();
                    // ....
                }
                else
               if(mouseEvent.getButton().equals(MouseButton.PRIMARY)){
                    if(mouseEvent.getClickCount() == 2){
                        System.out.println("Double clicked");
                       // mousePressedInListViewDC(mouseEvent);
                    }
                    else
                    if(mouseEvent.getClickCount() == 1){
                        System.out.println("1 clicked");
                        mousePressedInListView1C(mouseEvent);
                    }
               }
            }
        })

;

于 2021-09-04T09:46:30.340 回答
0

我遇到了同样的问题,我注意到单击和双击与 basic 区别:

    Button btn = new Button("Double click me too");
    btn.setOnMousePressed(mouseEvent -> {
        // CLICK catches
        if (mouseEvent.getClickCount() == 1) {
            System.out.println("Button clicked");
        } else if (mouseEvent.getClickCount() == 2)
            System.out.println("Button double clicked");
    });

但是作为双击的一部分,“单击”会被捕获。所以你会在控制台上看到: 在此处输入图像描述

主要使用@markus-weninger 的答案,我建立了一个扩展类Button以公开2个新EventHandlers:

  • setOnMouseSingleClicked(EventHandler<MouseEvent> eventHandler)
  • setOnMouseDoubleClicked(EventHandler<MouseEvent> eventHandler)

因此,使用下面的完整示例代码,当双击最后一个按钮时,我们得到: 在此处输入图像描述

请记住:

  1. 明显的缺点是,即使是单击捕获setOnMouseSingleClicked也会延迟singleClickDelayMillis(如 Kleopatra 所述,应根据操作系统设置的暴露变量)。
  2. 另一个值得注意的事实是,我扩展了Button,而不是Node它应该在的地方:实现 onMouseClicked(...) 的类。 在此处输入图像描述
  3. 作为最后的评论,我决定添加一个新的 EventHandler 而不是使用现有的setOnMousePressedsetOnMouseReleased或者setOnMouseClicked这样开发人员仍然可以完全实现这些便利EventHandler。例如,为了立即响应单击按钮而无需等待singleClickDelayMillis. 但这意味着,如果您同时实现两者,setOnMouseClicked即使双击也会被触发......当心。

代码来了:

import java.util.concurrent.CompletableFuture;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.beans.property.ObjectProperty;
import javafx.event.EventHandler;
import javafx.beans.property.SimpleObjectProperty;

public class DblClickCatchedWithoutSingleClick extends Application {

public class ButtonWithDblClick extends Button {

    private long        singleClickDelayMillis  = 250;
    private ClickRunner latestClickRunner       = null;

    private ObjectProperty<EventHandler<MouseEvent>>    onMouseSingleClickedProperty    = new SimpleObjectProperty<>();
    private ObjectProperty<EventHandler<MouseEvent>>    onMouseDoubleClickedProperty    = new SimpleObjectProperty<>();

    // CONSTRUCTORS
    public ButtonWithDblClick() {
        super();
        addClickedEventHandler();
    }

    public ButtonWithDblClick(String text) {
        super(text);
        addClickedEventHandler();
    }

    public ButtonWithDblClick(String text, Node graphic) {
        super(text, graphic);
        addClickedEventHandler();
    }

    private class ClickRunner implements Runnable {

        private final Runnable  onClick;
        private boolean         aborted = false;

        public ClickRunner(Runnable onClick) {
            this.onClick = onClick;
        }

        public void abort() {
            this.aborted = true;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(singleClickDelayMillis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!aborted) {
                Platform.runLater(onClick::run);
            }
        }
    }

    private void addClickedEventHandler() {
        //Handling the mouse clicked event (not using 'onMouseClicked' so it can still be used by developer).
        EventHandler<MouseEvent> eventHandler = me -> {
            switch (me.getButton()) {
                case PRIMARY:
                    if (me.getClickCount() == 1) {
                        latestClickRunner = new ClickRunner(() -> {
                            System.out.println("ButtonWithDblClick : SINGLE Click fired");
                            onMouseSingleClickedProperty.get().handle(me);
                        });
                        CompletableFuture.runAsync(latestClickRunner);
                    }
                    if (me.getClickCount() == 2) {
                        if (latestClickRunner != null) {
                            latestClickRunner.abort();
                        }
                        System.out.println("ButtonWithDblClick : DOUBLE Click fired");
                        onMouseDoubleClickedProperty.get().handle(me);
                    }
                    break;
                case SECONDARY:
                    // Right-click operation. Not implemented since usually no double RIGHT click needs to be caught.
                    break;
                default:
                    break;
            }
        };
        //Adding the event handler
        addEventHandler(MouseEvent.MOUSE_CLICKED, eventHandler);
    }

    public void setOnMouseSingleClicked(EventHandler<MouseEvent> eventHandler) {
        this.onMouseSingleClickedProperty.set(eventHandler);
    }

    public void setOnMouseDoubleClicked(EventHandler<MouseEvent> eventHandler) {
        this.onMouseDoubleClickedProperty.set(eventHandler);
    }
    public long getSingleClickDelayMillis() {
        return singleClickDelayMillis;
    }

    public void setSingleClickDelayMillis(long singleClickDelayMillis) {
        this.singleClickDelayMillis = singleClickDelayMillis;
    }

}

public void start(Stage stage) {
    VBox root = new VBox();

    Label lbl = new Label("Double click me");
    lbl.setOnMouseClicked(mouseEvent -> {
        // CLICK catches
        if (mouseEvent.getClickCount() == 2) {
            System.out.println("Label double clicked");
        } else if (mouseEvent.getClickCount() == 1)
            System.out.println("Label clicked");
    });

    Button btn = new Button("Double click me too");
    btn.setOnMousePressed(mouseEvent -> {
        // CLICK catches
        if (mouseEvent.getClickCount() == 1) {
            System.out.println("Button clicked");
        } else if (mouseEvent.getClickCount() == 2)
            System.out.println("Button double clicked");
    });

    ButtonWithDblClick btn2 = new ButtonWithDblClick("Double click me three ;-)");
    btn2.setOnMouseSingleClicked(me -> {
        System.out.println("BUTTON_2 : Fire SINGLE Click");
    });
    btn2.setOnMouseDoubleClicked(me -> {
        System.out.println("BUTTON_2 : Fire DOUBLE Click");
    });

    root.getChildren().add(lbl);
    root.getChildren().add(btn);
    root.getChildren().add(btn2);

    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
}

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

}

于 2021-12-27T08:50:02.177 回答