how to differentiate between a single click or double click on a table row in javafx

那年仲夏 提交于 2020-11-29 19:12:35

问题


I am trying to create a table in javafx that allows a user to click on a row to go to one page or double click the row to go to a different page. The problem is that the application registers the event of the single click, but does not wait to see if there is another double click. Is there a way to have the program wait and see if there is another click?

what i have so far looks similar to something like

TableView searchResults;
ObservableList<MovieRow> rows = FXCollections.observableArrayList();

private TableColumn<MovieRow, String> title;
title.setCellValueFactory(new PropertyValueFactory<>("mTitle"));

rows.add(new MovieRow("The cat in the hat"));

searchResults.setItems(rows);

searchResults.setRowFactory(tv -> {
    TableRow<MovieRow> row = new TableRow<>();
    row.setOnMouseClicked(event -> {
        MovieRow tempResult = row.getItem();

        if (event.getClickCount() == 1) {
            System.out.println(tempResult.getMTitle + " was clicked once");
        }else{
            System.out.println(tempResult.getMTitle + " was clicked twice");
        }
    });
    return row;
});

public class MovieRow{
    private String mTitle;
    
    public MovieRow(String title){
           mTitle = title;
    }

    public String getMTitle() {
            return mTitle;
    }
}

actual output

single click: The cat in the hat was clicked once
double click: The cat in the hat was clicked once

desired output

single click: The cat in the hat was clicked once
double click: The cat in the hat was clicked twice

I've only found results on handling double clicks by themselves or single clicks by themselves but not having both, so I'm not sure if this is even possible. Any help would be much appreciated. Thanks.


回答1:


There's no way to do this that's part of the API: you just have to code "have the program wait and see if there is another click" yourself. Note that this means that the single-click action has to have a slight pause before it's executed; there's no way around this (your program can't know what's going to happen in the future). You might consider a different approach (e.g. left button versus right button) to avoid this slightly inconvenient user experience.

However, a solution could look something like this:

public class DoubleClickHandler {

    private final PauseTransition delay ;
    private final Runnable onSingleClick ;
    private final Runnable onDoubleClick ;
    private boolean alreadyClickedOnce ;

    public DoubleClickHandler(
        Duration maxTimeBetweenClicks,
        Runnable onSingleClick,
        Runnable onDoubleClick) {

        alreadyClickedOnce = false ;
        this.onSingleClick = onSingleClick ;
        this.onDoubleClick = onDoubleClick ;

        delay = new PauseTransition(maxTimeBetweenClicks);
        delay.setOnFinished(e -> {
            alreadyClickedOnce = false ;
            onSingleClick.run()
        });
    }

    public void applyToNode(Node node) {
        node.setOnMouseClicked(e -> {
            delay.stop();
            if (alreadyClickedOnce) {
                alreadyClickedOnce = false ;
                onDoubleClick.run();
            } else {
                alreadyClickedOnce = true ;
                delay.playFromStart();
            }
        });
    }
}

Which you can use with:

searchResults.setRowFactory(tv -> {
    TableRow<MovieRow> row = new TableRow<>();
    DoubleClickHandler handler = new DoubleClickHandler(
        Duration.millis(500),
        () -> {
            MovieRow tempResult = row.getItem();
            System.out.println(tempResult.getMTitle + " was clicked once");
        },
        () -> {
            MovieRow tempResult = row.getItem();
            System.out.println(tempResult.getMTitle + " was clicked twice");
        }
    );
    handler.applyToNode(row);
    return row ;
});



回答2:


I encountered the same requirement once and worked on developing a custom event dispatcher. The solution what @James_D provided is clean, simple and works great. But if you want to generalize this behavior on a large scale, you can define a new custom mouse event and an event dispatcher.

The advantage of this approach is its usage will be just like other mouse events and can be handled in both event filters and handlers.

Please check the below demo and the appropriate code:

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

        // THIS IS THE PART OF CODE SETTING CUSTOM EVENT DISPATCHER
        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());
        }
    }
}


来源:https://stackoverflow.com/questions/65012787/how-to-differentiate-between-a-single-click-or-double-click-on-a-table-row-in-ja

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!