【问题标题】:Javafx 2 click and double clickJavafx 2 单击并双击
【发布时间】:2012-06-12 13:10:59
【问题描述】:

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

我想在单击和双击之间创建不同的事件。

谢谢

【问题讨论】:

    标签: java click javafx javafx-2 double-click


    【解决方案1】:

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

        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 而不是使用现有的setOnMousePressedsetOnMouseReleasedsetOnMouseClicked,以便开发人员仍然可以完全实现这些便利EventHandlers。例如,为了通过单击按钮立即响应而无需等待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();
    }
    

    }

    【讨论】:

      【解决方案2】:

      如果您要测试按下了多少个鼠标按钮 (==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);
                          }
                     }
                  }
              })
      

      ;

      【讨论】:

        【解决方案3】:

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

        【讨论】:

          【解决方案4】:

          不确定是否有人仍然遵循此 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());
                  }
              }
          }
          

          【讨论】:

            【解决方案5】:

            使用 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
                }
            });
            
            

            【讨论】:

              【解决方案6】:

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

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

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

              这样,要么单击执行双击操作,但永远不会同时执行。


              以下是完整代码。要使用它,只需要将三个 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;
                      }
                  });
              }
              

              【讨论】:

              • hmm ... 你如何获得 SingleClickDelay?通常它是一个可配置的操作系统值
              • 另外,如果 single 和 double 之间的区别如此重要,那么在 UX 中会有某种味道:double 应该增加 single,而不是完全不同的东西(但是,这个评论应该在问题和所有其他答案 )
              • @kleopatra 关于问题 1:是的,它取决于操作系统。我们目前的原型使用固定值,但应该有不同的方法来获得该值(查看 Windows 的注册表等)...关于问题 2:我不是 UX 开发人员,但我同意你的看法。我们只是在原型中使用它。我没有仔细检查OP的问题,它要求单击和双击不同的事件,我发布了我们的灵魂。 :D
              【解决方案7】:

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

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

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

              【讨论】:

                【解决方案8】:

                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...   
                }
                

                【讨论】:

                  【解决方案9】:

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

                  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.");
                      }
                  }
                  

                  【讨论】:

                    【解决方案10】:

                    这是我实现双击的方法

                    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); 
                    

                    }

                    【讨论】:

                    • 我认为考虑点击之间的时间很重要。不过,我会像这样在 if 子句中调整顺序:if( diff&gt;0 &amp;&amp; (diff/1000)&lt;=215)
                    【解决方案11】:

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

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

                    【讨论】:

                    • @uluk biy 我的 fxml 文件中注册并在单击按钮时调用的 onAction 处理程序怎么样。这个处理程序会和它发生冲突吗?
                    • @Anil。不,它不会。 Button 可以同时具有 onAction 和 onMouseClicked 事件处理程序。这些事件将按照每个事件的 API 文档所述触发。
                    • @uluk biy 是否应该单击处理代码进入 onAction 处理程序并双击鼠标处理程序?
                    • @Anil。它不应该但可能会这样。我认为,除非您知道自己在做什么,否则整体语义不会受到伤害。
                    • @Uluk Biy 我试过了,但没有用。双击鼠标调用 OnAction 处理程序,而不是也注册的这个处理程序。我使用 onAction 处理程序进行单击。我想知道在单击期间是否正在消耗事件 - 关于如何使其继续到双击处理程序的任何想法?
                    猜你喜欢
                    • 2016-05-31
                    • 2012-01-25
                    • 2018-02-04
                    • 2021-03-08
                    • 2014-07-22
                    • 2017-03-30
                    • 1970-01-01
                    • 2020-01-31
                    • 2012-09-12
                    相关资源
                    最近更新 更多