This discussion is archived
11 Replies Latest reply: Oct 31, 2012 11:44 AM by 893630 RSS

MouseEvent doubleClick Listener

893630 Newbie
Currently Being Moderated
Hi,
I have a requirement in which when you click a node you call "action A" and on double click you call "action B".

I was using the following code all this while:

+Node.setOnMouseClicked(new EventHandler<MouseEvent>(){+*
+@Override+
+public void handle(MouseEvent arg0) {+*
+if(arg0.getClickCount()==1){+*
+// do Action A.+
+}+
+if(arg0.getClickCount()==2){+*
+// do Action B.+
+}+
+}+
+});+

However, on double click-- this also triggers first click action . To make the matter worse, if Action A called by first click takes substantial time , then double click is interpreted as two single clicks not a double Click and action B never gets executed.
Is there any Listener to differentiate single and double click?
Pls advice appropriate approach for this.

Thanks
  • 1. Re: MouseEvent doubleClick Listener
    James_D Guru
    Currently Being Moderated
    I think this is necessarily a bit ugly.

    The behavior you observe is expected. Each click generates an independent event. The getClickCount() method returns a count of clicks within a small space of time (OS configurable) and a small (spatial) region. The same behavior happens in AWT.

    To detect a genuine single click, you need to wait and see if another click occurs before interpreting it as a single click. So something like this:
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.LabelBuilder;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.VBox;
    import javafx.scene.layout.VBoxBuilder;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.shape.RectangleBuilder;
    import javafx.stage.Stage;
    
    
    public class ClickCountTest extends Application {
      
      private boolean boxDoubleClicked ;
      
      private static final int DOUBLE_CLICK_WAIT_TIME = 400 ; // milliseconds
    
      @Override
      public void start(Stage primaryStage) throws Exception {
        boxDoubleClicked = false ;
        final Rectangle box = RectangleBuilder.create()
            .fill(Color.AQUAMARINE)
            .stroke(Color.BLACK)
            .width(200)
            .height(200)
            .build();
    
        final Label status = LabelBuilder.create()
            .text("Nothing clicked yet")
            .build();
        box.setOnMouseClicked(new EventHandler<MouseEvent>() {
          @Override
          public void handle(MouseEvent event) {
            if (event.getClickCount()==1) {
              processFirstClick(status);
            }
            if (event.getClickCount()==2) {
              processSecondClick(status);
            }
          }     
        });    
        VBox root = VBoxBuilder.create()
            .children(
                box, status
            )
            .build();
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.sizeToScene();
        primaryStage.show();
      }
    
      private void processFirstClick(final Label label) {
        new Thread(new Runnable() {
          @Override
          public void run() {
            try {
              Thread.sleep(DOUBLE_CLICK_WAIT_TIME);
              if (! isBoxDoubleClicked()) {
                Platform.runLater(new Runnable() {
                  @Override
                  public void run() {
                      label.setText("Single click");
                      System.out.println("Single click");
                    }
                });
              }
            } catch (InterruptedException exc) {
              // Should not be possible
              throw new Error(exc);
            }
          }
        }).start();
      }
      
      private void processSecondClick(final Label label) {
        setBoxDoubleClicked(true);
        label.setText("Double click");
        System.out.println("Double click");
        new Thread(new Runnable() {
          @Override
          public void run() {
            try {
              Thread.sleep(DOUBLE_CLICK_WAIT_TIME);
              setBoxDoubleClicked(false);
            } catch (InterruptedException exc) {
              // should not happen
              throw new Error(exc);
            }
          }
        }).start();
      }
      
      public synchronized void setBoxDoubleClicked(boolean doubleClicked) {
        boxDoubleClicked = doubleClicked ;
      }
      
      public synchronized boolean isBoxDoubleClicked() {
        return boxDoubleClicked ;
      }
      
      public static void main(String[] args) {
        launch(args);
      }
    
    }
    Here processFirstClick() waits a period of time, then checks the boxDoubleClicked flag. If the box has not been double clicked, it proceeds to change the state of the UI. Note that the waiting must happen on a different thread, and the change to the UI must happen on the FX application thread, using Platform.runLater(...).

    processSecondClick() in turn sets the boxDoubleClicked flag, then updates the UI. This all happens on the FX application thread as it was invoked by the event handler. It then starts a new thread which waits and then clears the boxDoubleClicked flag, so that subsequent single clicks get the correct value.

    You might want to tune the DOUBLE_CLICK_WAIT_TIME to get optimal behavior.

    Note the isBoxDoubleClicked and setBoxDoubleClicked methods must be synchronized as they change shared state from different threads.

    There may be easier ways to do this that I don't know about or can't see immediately: others will likely post if that is true.

    Incidentally, regarding your comment
    890627 wrote:
    To make the matter worse, if Action A called by first click takes substantial time , then double click is interpreted as two single clicks not a double Click and action B never gets executed.
    you really shouldn't do this. If an action takes substantial time, don't execute it on the FX application thread: launch a new thread to manage it and use Platform.runLater(...) to update the UI once it has done its time-consuming work.
  • 2. Re: MouseEvent doubleClick Listener
    952782 Newbie
    Currently Being Moderated
    looks legit.. but confused
  • 3. Re: MouseEvent doubleClick Listener
    893630 Newbie
    Currently Being Moderated
    Thanks James for your insight.
  • 4. Re: MouseEvent doubleClick Listener
    James_D Guru
    Currently Being Moderated
    You can probably clean this up considerably using java.util.Timer and java.util.TimerTask.
  • 5. Re: MouseEvent doubleClick Listener
    jsmith Guru
    Currently Being Moderated
    Multiple click handling strategies were discussed by the javafx development team.
    And they specifically decided not to add functionality to facilitate detecting situations like your example because it was considered "bad UI design".
    http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-May/001785.html

    For example if you take a look at the click handling on windows desktop icon:
    - one click to select and highlight an icon
    - double click to launch the app
    For the launch, it doesn't matter that the icon was first selected and highlighted or how many times it was selected and highlighted.

    So the suggestion is that you would change your application functionality such that:
    a. the first click action (Action A) should execute very quickly and not take a substantial time.
    b. it doesn't matter if action A is triggered multiple times.
    c. on double click (Action B) it doesn't matter that the first click action (Action A) is also triggered.

    You could use James code as a work-around, but I wouldn't advise it unless this behavior is absolutely critical to your app, especially if the work-around seems confusing.

    Overall, the JavaFX philosophy is to make common things simple and other things possible, your current UI design is one which falls in the latter class.
  • 6. Re: MouseEvent doubleClick Listener
    893630 Newbie
    Currently Being Moderated
    Thanks jsmith.
    I understand having action A executed promptly (perhaps by having it on different thread) might fix the problem .
    I was just not sure , if a double click requirement was worthy enough to get into thread mgmt stuff .
    Any way, I think now I should.

    Regards.
  • 7. Re: MouseEvent doubleClick Listener
    James_D Guru
    Currently Being Moderated
    FWIW I agree entirely with jsmith's post above. I was actually going to write something expressing that but had to go to a meeting. jsmith said it better anyway.

    If the actions you want to perform are mutually exclusive in their functionality, it makes sense for the ui inputs that cause them to be invoked to be mutually exclusive as well. Single-click and double-click on the same mouse button are not mutually exclusive; one is a subset of the other. Obviously, you may be writing to someone else's requirement document here, but if you have the option to change the requirements it's probably worth fighting that battle. There are a number of ways you could have similar but more appropriate input events to invoke the actions you want: for example a context menu with the two options, different mouse buttons, or (less preferably perhaps) using modifier keys on the mouse click.

    The code to do what you want, as you can see, is a little tricky and will be harder to maintain; the code sample I have is not really a particularly robust solution (it would take me a lot more time to make it so). Additionally the UI is likely to be harder to use (if the user is too slow with their double-click, for example, they will end up invoking a different, unrelated piece of functionality (twice) to the one they intended).
  • 8. Re: MouseEvent doubleClick Listener
    893630 Newbie
    Currently Being Moderated
    Hi James,
    I want to go with the idea of finishing action of first click fast enough so that multiple selection in multiple click wont be a problem.

    You have suggested to launch a new thread to manage doActionA() from first click.

    I have created an instance of the task having action A in its call() and run it in a new thread . But the problem is not solved yet. still, double click is getting interpreted as single click two times.
    I am sure I am missing something ...
    node.setOnMouseClicked(new EventHandler<MouseEvent>(){
          @Override
          public void handle(MouseEvent arg0) {
    
              //PART1
         if (arg0.getClickCount()==1){
                      Task<Void> task = new Task<Void>() {
                 @Override
                 protected Void call() throws Exception {
                   Platform.runLater(new Runnable() {
                     @Override
                     public void run() {
                      doActionA();
                     }
                  });
                   return null;
                 }
               };
    
             new Thread(task).start();
            }
    
         //PART 2
             if(arg0.getClickCount()==2){
            doActionB();
            }
    
          }
    Edited by: 890627 on Oct 30, 2012 4:42 PM
  • 9. Re: MouseEvent doubleClick Listener
    James_D Guru
    Currently Being Moderated
    For a double click, the code you posted should invoke action A (run on a background thread), then invoke action B. The first click of the double click will invoke the handle(...) method with a MouseEvent whose clickCount is 1; the second click will invoke the handle method with a MouseEvent whose clickCount is 2.

    If you're getting something different, post a complete executable example (the simplest one that shows the behavior).
  • 10. Re: MouseEvent doubleClick Listener
    James_D Guru
    Currently Being Moderated
    Oh, sorry; I see the issue now.

    Your code to handle the single click doesn't actually achieve the aim of running doActionA() on a different thread. It launches a new thread which merely schedules doActionA() to be invoked on the FX application thread. So if doActionA() takes a long time, you're still tying up the FX Application thread (preventing it from detecting the second click of the double click) until it's complete.

    The structure you want is
    public void handle(MouseEvent evt) {
      if (evt.getClickCount()==1) {
              Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                  doActionA();
                return null;
              }
           };
    
          new Thread(task).start();
       }
       ...
    }
    ...
    private void doActionA() {
       // do time-consuming work that doesn't change the UI here:
       // ...
       Platform.runLater(new Runnable() {
        @Override
        public void run() {
           // now update the UI with the results of your work here
           //...
        }
      });
    }
  • 11. Re: MouseEvent doubleClick Listener
    893630 Newbie
    Currently Being Moderated
    Thanks , got it working now.

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points