【问题标题】:Detecting when the mouse has 'stopped moving' in JavaFX using FXML使用 FXML 在 JavaFX 中检测鼠标何时“停止移动”
【发布时间】:2015-11-17 08:38:23
【问题描述】:

我希望能够使用 JavaFX 检测鼠标何时移动以及何时不在 AnchorPane 上移动。

我目前在我的窗格中有一个“鼠标移动事件”,它在鼠标移动时输出一些文本,但还想在鼠标不移动时包含一些输出。
我不知道如何在保持相对简单的同时解决问题的后半部分。

每当鼠标移动时,以下输出“正在移动”,但当鼠标停止时,什么也没有发生。我可以看到这是因为 checkMovement 仅在它移动时被调用。

是否有反函数或类似的东西——会做相反的事情?

我确实相信我可以使用特定的事件处理程序和/或计时器来实现我想要的,但这对于看起来很简单的事情来说过于复杂了。
我是否遗漏了一些非常明显的东西??
我只是使用 FX 的新手,所以请随时指出我应该以不同的方式做的任何事情。

编辑: 我确实忘记指定我使用 FXML 来定义控件。 我需要将此过程放在控制器(AppController.java)中还是为了“易于使用”而将其留在 Main.java 中?
我什至会怎么做/这可能吗? 显然mouseMovementMethod需要修改。

AppController.Java

公共类 AppController 实现 Initializable{

    boolean moved = false;

    @FXML 
    private AnchorPane anchorPane;

    @FXML
    private ImageView killpic;

    @FXML
    private ImageView processpic;

    @FXML
    private Text exit;

   @FXML
   private Button processAnimalButton;

   /*
    * ...Other Variables
    */



    @FXML 
    void mouseMovedMethod(Event event){
        //Mouse Movement Event
    }

    @FXML
    void closeMenu(Event event) {
        Stage stage = (Stage) exit.getScene().getWindow();
        stage.close();
    }

    @FXML
    void selectProcess(Event event) {
        Image killimg = new Image("/images/knife_blood.png");
        Image proimg = new Image("/images/p_selected.png");
        killpic.setImage(killimg);
        processpic.setImage(proimg);
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        Image killimg = new Image("/images/knife_blood_selected.png");
        Image proimg = new Image("/images/p.png");
        killpic.setImage(killimg);
        processpic.setImage(proimg);
    }

}

Main.java:

//*Required imports*//

public class Main extends Application {
    Stage thestage;

    @Override
    public void start(Stage primaryStage) {
        try {
            AnchorPane root = (AnchorPane)FXMLLoader.load(getClass().getResource("popup.fxml"));
            Scene scene = new Scene(root);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            thestage = primaryStage;    
            primaryStage.setScene(scene);
            primaryStage.show();

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

【问题讨论】:

  • 每次mouseMoved 被触发时,都会以短暂的延迟重新启动某种计时器。当定时器触发时,鼠标将停止移动。您可能还想跟踪鼠标退出事件

标签: java javafx


【解决方案1】:

这并不像你想象的那么简单,原因在于“鼠标不动”的概念并没有你想象的那么明确。底层操作系统在一些深埋的内部循环中检测输入事件,例如鼠标的移动。该循环需要有限的时间来运行,因此鼠标在循环运行的时间内总是明显静止的。操作系统将以降低的频率向 JavaFX 工具包报告这些动作,而 JavaFX 工具包可能会进一步限制这些动作的报告时间。

因此,要定义“鼠标不动”,您确实必须求助于“在最后一个x 中未检测到鼠标移动”,其中x 是某种时间度量(实时,例如纳秒,或帧渲染的数量)。所以你真的必须以某种方式实际实现逻辑,并决定多少时间算作“不动”。

@MadProgrammer 建议在 cmets 中使用一种实现(我认为这是更简单的一种),@Roland 建议使用另一种。

这是@MadProgrammer 建议的实现:

import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TrackMouseStopped extends Application {

    private final long MIN_STATIONARY_TIME = 100_000_000 ; // nanoseconds 

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();

        BooleanProperty mouseMoving = new SimpleBooleanProperty();
        mouseMoving.addListener((obs, wasMoving, isNowMoving) -> {
           if (! isNowMoving) {
               System.out.println("Mouse stopped!");
           }
        });

        PauseTransition pause = new PauseTransition(Duration.millis(MIN_STATIONARY_TIME / 1_000_000));
        pause.setOnFinished(e -> mouseMoving.set(false));

        // Note: if you want to consider the mouse having moved for
        // other events (e.g. dragging), you can do
        // pane.addEventHandler(MouseEvent.ANY, e -> { ... }); here
        pane.setOnMouseMoved(e -> {
            mouseMoving.set(true);
            pause.playFromStart();
        });

        Scene scene = new Scene(pane, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

这是@Roland 建议的实现:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class TrackMouseStopped extends Application {

    private final long MIN_STATIONARY_TIME = 100_000_000 ; // nanoseconds 

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();
        ObjectProperty<Point2D> mouseLocation = new SimpleObjectProperty<>(new Point2D(0, 0));
        pane.setOnMouseMoved(e -> mouseLocation.set(new Point2D(e.getX(), e.getY())));

        BooleanProperty mouseMoving = new SimpleBooleanProperty();
        mouseMoving.addListener((obs, wasMoving, isNowMoving) -> {
           if (! isNowMoving) {
               System.out.println("Mouse stopped!");
           }
        });

        AnimationTimer timer = new AnimationTimer() {
            private double lastMouseX ;
            private double lastMouseY ;
            long lastMouseMovement ;
            @Override
            public void handle(long timestamp) {
                double x = mouseLocation.get().getX();
                double y = mouseLocation.get().getY();
                if (Double.doubleToLongBits(lastMouseX) != Double.doubleToLongBits(x)
                    || Double.doubleToLongBits(lastMouseY) != Double.doubleToLongBits(y)) {
                    lastMouseMovement = timestamp ;
                }

                mouseMoving.set(timestamp - lastMouseMovement < MIN_STATIONARY_TIME);

                lastMouseX = x;
                lastMouseY = y;
            }
        };
        timer.start();

        Scene scene = new Scene(pane, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

【讨论】:

  • 将此答案标记为已接受,因为它提供了两种方法的实现。 Roland 和 MadProgrammer 的方法完全有效。
【解决方案2】:

创建MouseEvent.ANY 的事件监听器。在该事件侦听器中,您将自己的属性 mouseX 和 mouseY 设置为事件的鼠标位置值。除此之外,创建一个永久运行的AnimationTimer,并检查鼠标坐标是否在“delta”毫秒的间隔内没有改变。如果没有,请调用您的处理代码。

例子:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;


public class Main extends Application {

    Scene scene;
    MouseStatus mouseStatus = new MouseStatus();

    Label infoLabel;

    @Override
    public void start(Stage primaryStage) {

        Group root = new Group();

        infoLabel = new Label();
        root.getChildren().add( infoLabel);

        scene = new Scene(root, 640, 480);

        primaryStage.setScene(scene);
        primaryStage.show();

        addInputListeners();

    }


    private void addInputListeners() {

        scene.addEventFilter(MouseEvent.ANY, e -> {

            infoLabel.setText("Moving");

            mouseStatus.setX(e.getX());
            mouseStatus.setY(e.getY());
            mouseStatus.setPrimaryButtonDown(e.isPrimaryButtonDown());
            mouseStatus.setSecondaryButtonDown(e.isSecondaryButtonDown());

        });

        AnimationTimer loop = new AnimationTimer() {

            long deltaNs = 30_000_000;

            double currX;
            double currY;
            long currNs;

            double prevX;
            double prevY;
            long prevNs;

            @Override
            public void handle(long now) {

                currX = mouseStatus.x;
                currY = mouseStatus.y;
                currNs = now;

                if( currNs - prevNs > deltaNs) {

                    if( prevX == currX && prevY == currY) {
                        infoLabel.setText("Stopped");
                    }

                    prevX = currX;
                    prevY = currY;
                    prevNs = currNs;
                }

            }
        };
        loop.start();

    }



    public class MouseStatus {

        double x;
        double y;
        boolean primaryButtonDown;
        boolean secondaryButtonDown;

        public double getX() {
            return x;
        }
        public void setX(double x) {
            this.x = x;
        }
        public double getY() {
            return y;
        }
        public void setY(double y) {
            this.y = y;
        }
        public boolean isPrimaryButtonDown() {
            return primaryButtonDown;
        }
        public void setPrimaryButtonDown(boolean primaryButtonDown) {
            this.primaryButtonDown = primaryButtonDown;
        }
        public boolean isSecondaryButtonDown() {
            return secondaryButtonDown;
        }
        public void setSecondaryButtonDown(boolean secondaryButtonDown) {
            this.secondaryButtonDown = secondaryButtonDown;
        }


    }

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

}

【讨论】:

  • 当您发布此内容时,我只是在测试这个想法。除非我错过了一种方法,否则它比@MadProgrammer 的建议要复杂得多。
  • @James_D:添加了示例。您会使用哪个计时器来一遍又一遍地启动?当然,这也是有效的,但我个人更喜欢 AnimationTimer 的连续性。顺便说一句,MadProgrammer 的解决方案不考虑 e。 G。移动时按下鼠标,i。 e.拖动。
  • 添加示例:抱歉,只是与您的交叉发布,无意窃取这个想法。 PauseTransition 是最简单的计时器。假设这是所需的功能,使用 MouseEvent.ANY 是一个不错的选择。
  • 没问题。你的很紧凑。我已经用我的粒子东西,在画布上绘画等有一段时间了。在我需要的鼠标位置中,例如。 G。鼠标按钮被按下,什么不是。所以对我来说,这只是复制/粘贴。当然可以删除冗余代码。
  • 如果您使用 FXML,这没有任何区别。为方便起见,发布的示例只是独立的示例。只需在initialize() 方法中进行初始化工作即可。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多