【问题标题】:How to fire mouse event programmatically in JavaFX?如何在 JavaFX 中以编程方式触发鼠标事件?
【发布时间】:2016-10-14 11:04:47
【问题描述】:

以下代码显示两个面板,黄色和蓝色,其中蓝色是黄色的子代。

如果我点击蓝色面板的中心,两个面板都会处理鼠标事件。

如何以编程方式模拟相同的行为?

Fire event 按钮中,我尝试这样做,但失败了:生成的事件似乎只能由黄色窗格处理。

是否可以发布事件以便所有子级处理它,就好像它发生在“本地”事件中一样?

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class FireMouseEventProgrammatically extends Application {

   @Override
   public void start(Stage primaryStage) throws Exception {

      BorderPane root = new BorderPane();




      AnchorPane yellowPane = new AnchorPane();
      yellowPane.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY)));
      yellowPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
         @Override
         public void handle(MouseEvent event) {
            System.out.print("yellowPane clicked ");
            printMouseEvent(event);
            System.out.println("");
         }
      });

      root.setCenter(yellowPane);


      Pane bluePane = new Pane();
      bluePane.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY)));
      bluePane.setOnMouseClicked(new EventHandler<MouseEvent>() {
         @Override
         public void handle(MouseEvent event) {
            System.out.print("bluePane clicked ");
            printMouseEvent(event);
            System.out.println("");
         }
      });

      AnchorPane.setLeftAnchor(bluePane, 200.);
      AnchorPane.setRightAnchor(bluePane, 200.);
      AnchorPane.setTopAnchor(bluePane, 200.);
      AnchorPane.setBottomAnchor(bluePane, 200.);


      yellowPane.getChildren().add(bluePane);




      FlowPane buttonPane = new FlowPane();
      buttonPane.setHgap(5);
      buttonPane.setPadding(new Insets(5, 5, 5, 5));

      root.setBottom(buttonPane);



      Button fireEventButton = new Button();
      fireEventButton.setText("Fire event");
      fireEventButton.setOnAction(new EventHandler<ActionEvent>() {
         @Override
         public void handle(ActionEvent event) {

            double blueX = bluePane.getWidth()/2;
            double blueY = bluePane.getHeight()/2;

            Point2D screenCoords = bluePane.localToScreen(blueX, blueY);
            Point2D sceneCoords = bluePane.localToScene(blueX, blueY);

            Event.fireEvent(yellowPane, new MouseEvent(MouseEvent.MOUSE_CLICKED,
               sceneCoords.getX(), sceneCoords.getY(), screenCoords.getX(), screenCoords.getY(), MouseButton.PRIMARY, 1,
               true, true, true, true, true, true, true, true, true, true, null));

         }
      });

      buttonPane.getChildren().add(fireEventButton);


      Scene scene = new Scene(root, 800, 600);
      primaryStage.setScene(scene);
      primaryStage.show();

   }

   private void printMouseEvent(MouseEvent event) {
      System.out.print(String.format("x = %f, y = %f, xScreen = %f, yScreen = %f", event.getX(), event.getY(), event.getScreenX(), event.getScreenY()));
   }

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

}

更新

我不能明确提及子节点,因为在一般情况下它可以是许多子节点。

我怎么知道要定位哪一个?

我想传递坐标,让系统自动确定目标。

【问题讨论】:

标签: java events javafx


【解决方案1】:

实际(物理)点击和您的程序点击之间的区别在于,在第一种情况下,原始事件目标是 bluePane(因为它在视觉上是“最上面的”Node),在后一种情况下目标是yellowPane

所以,当路由构造发生时(请参考Event Delivery Process section),在第一种情况下,路由将是root -> yellowPane -> bluePane,而在第二种情况下,它将只有root -> yellowPane,因此bluePane 没有受到事件的影响。

基于此,您可以定位bluePane

Event.fireEvent(bluePane, new MouseEvent(MouseEvent.MOUSE_CLICKED,
   sceneCoords.getX(), sceneCoords.getY(), screenCoords.getX(), screenCoords.getY(), MouseButton.PRIMARY, 1,
   true, true, true, true, true, true, true, true, true, true, null));

或者您可以使用Robot 来生成点击:

try {
    robot = new java.awt.Robot();

    robot.mouseMove((int)screenCoords.getX(), (int)screenCoords.getY());
    robot.mousePress(16);
    robot.mouseRelease(16);
    robot.mouseMove((int) originalLocation.getX(), (int)originalLocation.getY());
} catch (AWTException e) {
    e.printStackTrace();
}

更新 1:

如果您想坚持 JavaFX 方式,现在的问题是如何确定某个位置的“最上层”Node

这是一件不可能以干净的方式实现的事情。已经有一个feature request 与此有关。

我在fxexperience 的辅助方法上找到了这个,它可以用来确定某个位置上的Node

public static Node pick(Node node, double sceneX, double sceneY) {
    Point2D p = node.sceneToLocal(sceneX, sceneY, true /* rootScene */);

    // check if the given node has the point inside it, or else we drop out
    if (!node.contains(p)) return null;

    // at this point we know that _at least_ the given node is a valid
    // answer to the given point, so we will return that if we don't find
    // a better child option
    if (node instanceof Parent) {
        // we iterate through all children in reverse order, and stop when we find a match.
        // We do this as we know the elements at the end of the list have a higher
        // z-order, and are therefore the better match, compared to children that
        // might also intersect (but that would be underneath the element).
        Node bestMatchingChild = null;
        List<Node> children = ((Parent)node).getChildrenUnmodifiable();
        for (int i = children.size() - 1; i >= 0; i--) {
            Node child = children.get(i);
            p = child.sceneToLocal(sceneX, sceneY, true /* rootScene */);
            if (child.isVisible() && !child.isMouseTransparent() && child.contains(p)) {
                bestMatchingChild = child;
                break;
            }
        }

        if (bestMatchingChild != null) {
            return pick(bestMatchingChild, sceneX, sceneY);
        }
    }

    return node;
}

你可以像这样使用:

Node pick = pick(root, sceneCoords.getX(), sceneCoords.getY());
Event.fireEvent(pick, new MouseEvent(MouseEvent.MOUSE_CLICKED,
   sceneCoords.getX(), sceneCoords.getY(), screenCoords.getX(), screenCoords.getY(), MouseButton.PRIMARY, 1,
   true, true, true, true, true, true, true, true, true, true, null));

更新2:

您还可以使用不推荐使用的方法 Node.impl_pickNode(PickRay pickRay, PickResultChooser result) 在场景位置上获取相交的Node

PickRay pickRay = new PickRay((int) sceneCoords.getX(), (int) sceneCoords.getY(), 1.0, 1.0, 1.0);
PickResultChooser pickResultChooser = new PickResultChooser();
root.impl_pickNode(pickRay,  pickResultChooser);
Node intersectedNode = pickResultChooser.getIntersectedNode();

您也可以类似地使用此Node 作为目标。

【讨论】:

  • 我只得到参数Event.fireEvent(EventTarget eventTarget, Event event);。我最近在这个答案中使用了 javaFX.event.Event 的另一组参数。知道发生了什么吗?为什么我不能使用Event.fireEvent(EventType&lt;? extends MouseEvent&gt; eventType, double x, double y...
  • Event.fireEvent 接受两个参数:第一个是EventTargetpick,这是一个实现EventTargetNode),第二个是Event(即new MouseEvent(...)).
  • 啊,我把new MouseEvent(...)构造函数参数混为Event.fireEvent()方法的参数。谢谢。
  • 我能以某种方式将屏幕设置为事件目标吗?我想触发一个事件而不必指定节点。我不想使用awt.Robot,因为我还需要进行触摸测试。
  • 我担心这不适用于 JavaFX 事件。为此,我能建议的最好的就是机器人。
【解决方案2】:

MouseEvent 的位置在蓝色和黄色窗格内(蓝色窗格在黄色窗格内)。如果你这样做:

Event.fireEvent(bluePane, new MouseEvent(MouseEvent.MOUSE_CLICKED,
               sceneCoords.getX(), sceneCoords.getY(), screenCoords.getX(), screenCoords.getY(), MouseButton.PRIMARY, 1,
               true, true, true, true, true, true, true, true, true, true, null));

它将按您的预期工作。

JavaFX 无法根据鼠标事件的位置确定单击了哪个组件。您必须明确传递事件的目标。在这种情况下,它将是 bluePane。

您可以在此处找到更多信息: https://docs.oracle.com/javafx/2/events/processing.htm

【讨论】:

    【解决方案3】:

    如果我理解正确的话,你想要获得 yel​​lowPane 的孩子,并触发他们的每个事件。

    我是这样完成的

    其中ObservableList&lt;Node&gt; nodesyellowPane.getChildren() 的结果

    private void clickOnMe(ObservableList<Node> nodes){
            for(Node n : nodes){
                n.fireEvent(new MouseEvent(MouseEvent.MOUSE_CLICKED,
                        n.getLayoutX(), n.getLayoutY(), n.getLayoutX(), n.getLayoutY(), MouseButton.PRIMARY, 1,
                        true, true, true, true, true, true, true, true, true, true, null));
            }
        }
    

    这将抓住yellowPane 的每个孩子,你说可能很多,并触发他们的每个事件。希望这是你想要的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 2019-06-18
      相关资源
      最近更新 更多