Javafx 2 click and double click
Asked Answered
S

11

63

I would like to know if it was possible to detect the double-click in JavaFX 2 ? and how ?

I would like to make different event between a click and a double click.

Thanks

Shari answered 8/6, 2012 at 13:7 Comment(0)
M
108

Yes you can detect single, double even multiple clicks:

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 is used to determine if the left (commonly) mouse button is triggered the event. Read the api of getClickCount() to conclude that there maybe multiple click counts other than single or double. However I find it hard to distinguish between single and double click events. Because the first click count of the double click will rise a single event as well.

Marigraph answered 8/6, 2012 at 14:30 Comment(10)
@uluk biy what about the onAction handler that is registered in my fxml file and is called when the button is clicked. Will this handler clash with it?Quarto
@Anil. No it will not. The Button can has both onAction and onMouseClicked event handlers. These events will be triggered as the API doc of each them says.Marigraph
@uluk biy should single click handling code go in onAction handler and double click in the mouse handler?Quarto
@Anil. It not should but may go that way. The overall semantic will not hurt unless you know what you are doing, I think.Marigraph
@Uluk Biy I tried it and it did not work. Double clicking the mouse calls the OnAction handler, not this handler which was also registered. I use the onAction handler for single clicks. I wonder if during single click, the event is being consumed - any ideas on how to make it continue on to the double click handler?Quarto
@Uluk Biy the buttons are defined in my fxml file with fx:id="buttonA". In my controller, I call buttonA.setOnMouseClicked(...Quarto
@Uluk Biy, your method unfortunately did not work for me. See #14349501Quarto
@MJafarMash - you are correct - since MouseButton is an enum, they are equivalent. See also this question.Baluchi
if I want to fire a click event should that event then be given 0 or 1 in clickcount? I want to fire a simple/single mouse left click.Blackleg
Simple and usefull. Thank you.Othello
A
7

Here is another piece of code which can be used if you have to distinguish between a single- and a double-click and have to take a specific action in either case.

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.");
    }
}
Amrita answered 27/3, 2016 at 9:26 Comment(0)
M
4

Adhering to Java SE 8 lambda expressions would look something like this:

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

Once you get used to lambda expressions - they end up being more understandable than the original class instantiation and overriding (x) method. -In my opinion-

Madson answered 8/3, 2018 at 21:14 Comment(0)
I
3

The response by P. Pandey is the simplest approach which actually distinguishes between single and double click, but it did not work for me. For one, the function "currentTimeMillis" already returns milliseconds, so dividing it by 1000 does not seem to be necessary. The version below worked for me in a more consistent fashion.

 @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...   
}
Isolt answered 18/7, 2016 at 3:18 Comment(0)
K
3

A solution using 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
    }
});

Korney answered 20/10, 2019 at 11:2 Comment(1)
This answer belongs to the ones that make a real distinction between single and double click, and provides a very elegant solution.Anyone
S
3

Not sure if someone still follows this OP or refer it, but below is my version of differentiating single click to double click. While most of the answers are quite acceptable, it would be really useful if it can be done in a proper resuable way.

One of the challenge I encountered is the need to have the single-double click differentiation on multiple nodes at multiple places. I cannot do the same repetitive cumbersome logic on each and every node. It should be done in a generic way.

So I opted to implement a custom EventDispatcher and use this dispatcher on node level or I can apply it directly to Scene to make it applicable for all child nodes.

For this I created a new MouseEvent namely 'MOUSE_DOUBLE_CLICKED", so tthat I am still sticking with the standard JavaFX practises. Now I can include the double_clicked event filters/handlers just like other mouse event types.

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

Below is the implementation and complete working demo of this custom event dispatcher.

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());
        }
    }
}
Sumptuous answered 30/10, 2019 at 0:34 Comment(0)
W
2

Here is how I have implemented double click

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

}

Wylma answered 12/2, 2016 at 12:3 Comment(1)
I think it´s important to take the time passed between the clicks into account. I´d turn round the order inside your if-clause like this, though: if( diff>0 && (diff/1000)<=215)Wexford
S
2

Since it is not possible to distinguish between single-click and double-click by default, we use the following approach:

On single-click, we wrap the single-click operation in an abortable runnable. This runnable waits a certain amount of time (i.e., SINGLE_CLICK_DELAY) before being executed.

In the meantime, if a second click, i.e., a double-click, occurs, the single-click operation gets aborted and only the double-click operation is performed.

This way, either the single-click or the double-click operation is performed, but never both.


Following is the full code. To use it, only the three TODO lines have to be replaced by the wanted handlers.

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;
        }
    });
}
Splendid answered 21/3, 2018 at 9:38 Comment(3)
hmm ... how do you get the SingleClickDelay? Typically it's a configurable OS valuePour
also, if the distinction between single and double is so important, there's a certain smell in the UX: the double should augment the single, not so something completely different (but then, this comment should be on the question and every other answer <g>)Pour
@Pour Regarding question 1: Yes, it is OS dependent. We currently live with a fixed value for our prototype, but there should be different ways to obtain that value (looking in Windows's registry, etc.)... Regarding question 2: I am no UX developer, but I agree with you. We just use it within a prototype. I did not scrutinize OP's question, it asked for different events for single-click and double-click, I posted our soultion. :DSplendid
C
0

An alternative to single click vs. double click that I'm using is single click vs. press-and-hold (for about a quarter to a half second or so), then release the button. The technique can use a threaded abortable timer as in some of the code snippets above to distinguish between the two. Assuming that the actual event handling happens on the button release, this alternative has the advantage that single click works normally (i.e., without any delay), and for press-and-hold you can give the user some visual feedback when the button has been held long enough to be released (so there's never any ambiguity about which action was performed).

Comate answered 5/1, 2020 at 15:16 Comment(0)
M
0

If you are testing how many mouse buttons (==2) are pressed, do not code it in sub-method! The next is working:

listView.setOnMouseClicked(me -> {
    if (me.getButton().equals(MouseButton.SECONDARY)) {
        System.out.println("isSecondaryButtonDown");
        me.consume();
        // ....
    } else if (me.getButton().equals(MouseButton.PRIMARY)) {
        if (mouseEvent.getClickCount() == 2) {
            System.out.println("Double clicked");
            // mousePressedInListViewDC(me);
        } else if (mouseEvent.getClickCount() == 1) {
            System.out.println("1 clicked");
            mousePressedInListView1C(me);
        }
    }
});
Moller answered 4/9, 2021 at 9:46 Comment(0)
W
0

I ran in the same problem, and what I noticed is that single and double click ARE distinguished with 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");
    });

But a 'single' click is catched as part of the double click. So you will see on the console : enter image description here

Using mainly the answer of @markus-weninger, I built up a Class extending Button to expose 2 new EventHandlers :

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

So with the full example code bellow, when double clicking on last button, we get : enter image description here

Keep in mind :

  1. The obvious drawback is that even a single click caught with setOnMouseSingleClicked will be delayed with the singleClickDelayMillis (exposed variable which should be set accordingly to the OS, as mentioned by Kleopatra).
  2. Another noticeable fact, is that I extended Button, and not Node where it should be : The Class where the onMouseClicked(...) is implemented. enter image description here
  3. As a last comment, I decided to add a new EventHandler rather than using the existing setOnMousePressed, setOnMouseReleased or setOnMouseClicked so that the developer can still fully implement these convenience EventHandlers. For example in order to have immediate response from a click on the button without waiting for the singleClickDelayMillis. But this means that if you implement both, the setOnMouseClicked will be fired even on a double click... beware.

Here comes the code :

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

}

Wehrmacht answered 27/12, 2021 at 8:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.