7

The following code show two panels, yellow and blue, where blue is a child of yellow.

If I click into the center of blue panel, mouse event is handled with both panels.

How to simulate the same behavior programmatically?

In the Fire event button I tried to do this, but failed: generated event appeared to handled only by yellow pane.

Is it possible to post event so that it processed by all children as if it happen with "native" events?

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

}

UPDATE

I can't mention child node explicitly, because in general case it can be numerous number of children.

How would I know which one to target?

I would like to pass coordinates and let system determine targets automatically.

Dims
  • 47,675
  • 117
  • 331
  • 600
  • Isn't that somewhat related to [the question you've asked yesterday](http://stackoverflow.com/q/40023260/3429133)? – beatngu13 Oct 14 '16 at 15:07

3 Answers3

9

The difference between the actual (phisycal) clicking and your programmatic clicking is, that in the first case the original event target is bluePane (as it is the "uppermost" Node visually), in the latter case the target is yellowPane.

So, when the route construction occurs (please refer to the Event Delivery Process section) in the first case the route will be root -> yellowPane -> bluePane, and in the second case it will be only root -> yellowPane, hence the bluePane is untouched by the event.

Based on this you can target the 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));

Or you can use a Robot for example to generate the click:

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

Update 1:

If you want to stick with the JavaFX way, the question is now how to determine the "uppermost" Node on a position.

This is one thing that is not possible in a clean way. There is already a feature request about this.

I have found this on helper method on fxexperience that can be used to determinte the Node on a certain position:

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

that you can use like:

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

Update2:

You can also use the deprecated method Node.impl_pickNode(PickRay pickRay, PickResultChooser result) to get the intersected Node on a scene position:

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

You can use also this Node as target similarly.

DVarga
  • 21,311
  • 6
  • 55
  • 60
  • I only get the parameters `Event.fireEvent(EventTarget eventTarget, Event event);`. I recently used the other set of parameters for the javaFX.event.Event like in this answer. Any idea what happened? Why I cannot use `Event.fireEvent(EventType extends MouseEvent> eventType, double x, double y`... – Lealo Sep 28 '17 at 10:10
  • 1
    `Event.fireEvent` takes two arguments: first one is an `EventTarget` (`pick`, which is a `Node` which implements `EventTarget`), second one is an `Event` (which is `new MouseEvent(...)`). – DVarga Sep 28 '17 at 10:27
  • Ah, I mixed up the `new MouseEvent(...)` contructor parameters as the being the parameters of the `Event.fireEvent()` method. Thank you. – Lealo Sep 28 '17 at 11:25
  • Can I set the screen as the eventtarget somehow? I would like to fire an event without having to specify the node. I do not wish to use `awt.Robot` beacuse I need to do testing with touch aswell. – Lealo Sep 28 '17 at 15:11
  • 1
    I fear that does not work with JavaFX events unfortunately. For that the best I could propose is the robot. – DVarga Sep 28 '17 at 15:17
  • What is the `screenCoords` parameters? – Lealo Sep 28 '17 at 16:50
  • May I kindly ask any of you to explain what "firing an event" means (what this method `fireEvent` achieves? I looked the [documentation of the method](https://docs.oracle.com/javase/8/javafx/api/javafx/event/Event.html#fireEvent-javafx.event.EventTarget-javafx.event.Event-) but I didn't understand what the targetEvent does. I would appreciate any insight! – KareemJ May 29 '20 at 20:31
3

The location of your MouseEvent is within both blue and yellow panes (blue one is inside yellow). If you do:

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

It will work as You expected.

JavaFX could not figure out what component was clicked base on location of a mouse event. You have to explicitly pass the target of the event. In that case it would be bluePane.

More information you can find here: https://docs.oracle.com/javafx/2/events/processing.htm

Dominik Kunicki
  • 1,037
  • 1
  • 12
  • 35
0

If I understood you correctly, you want to get the children of the yellowPane, and fire each of their events.

I accomplished this like so

Where ObservableList<Node> nodes is the result of yellowPane.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));
        }
    }

This will grab every child of yellowPane, which you said could be many, and fire each of their events. Hope this is what you wanted.

Hypnic Jerk
  • 1,192
  • 3
  • 14
  • 32