I had a lot of fun playing with mouse events while building our PageFlow PDF Viewing mode in JavaFX, so thought it worth writing a quick tutorial and sharing what I have learned.
To begin, there are a few API documentation pages that I recommend being familiar with – the Node class (the class that you set event handlers on), the EventHandler interface (what you set on nodes), and the two most obvious types of InputEvents – the MouseEvent and KeyEvent classes (the events to be handled). To see the other types of events JavaFX offers, they are all subclasses of the InputEvent class.
The Node class has a wide range of event listeners for the different types of events. Handily they are prefixed by what event they listen for, so all mouse event listeners begin with .onMouse, all key event listeners begin with .onKey.
So to listen to events we need to create EventHandlers for the type of event we want to listen to. From the EventHandler interface, we can see that we need to implement the handle method, so we define EventHandlers like so:
EventHandler handler = new EventHandler() {
public void handle (MouseEvent mouseEvent) {
// Handle here.
}
};
The mouseEvent object that got passed in contains information about the event such as the x & y position.
So to add a mouseEvent to the scene that fires when the scene gets clicked and outputs the mouse coordinates, we can define this like so:
scene.setOnMousePressed(new EventHandler() {
public void handle (MouseEvent mouseEvent) {
System.out.println("X: " + mouseEvent.getX() + " Y: " + mouseEvent.getY());
}
});
Simple! Now onto the fun bit.
In our PageFlow mode (which you can see a video of on YouTube here), if you click on a page, the mode will scroll the pages to the center to that page.
My first thought was that this would require the use of the .setOnMouseClicked listener which would get set on the page nodes. This worked fine but had an interesting ‘feature’. In the PageFlow mode we also listen for drags and adjust the positioning of the pages accordingly. Essentially we are either dragging the pages around or clicking on them to make the mode center on the page clicked on.
What I wasn’t aware of is that the onMouseClicked event can also include a drag. So if you click on something, drag it a little and then release it, the events you get are:
1. Mouse Pressed
2. Mouse Dragged
3. Mouse Released
4. Mouse Clicked
But the click event will only fire if both the press and release happen on that node. What this meant for my PageFlow mode is that if you press a page, drag it away from the center and then let go, if the cursor is still on that page when you let go, the mode would scroll that page back to the center because it thinks you just clicked on it.
What my code required is manual detection of a click that doesn’t include a drag like so:
setOnMousePressed(new EventHandler() {
public void handle(MouseEvent e) {
pageClickEvent = true;
}
});
setOnMouseDragged(new EventHandler() {
public void handle(MouseEvent e) {
pageClickEvent = false;
}
});
setOnMouseReleased(new EventHandler() {
public void handle(MouseEvent e) {
if (pageClickEvent) {
goTo(page);
pageClickEvent = false;
}
}
});
Another important thing to note is that mouse events fall through nodes. In the PageFlow mode, if you press a page, this results in an event firing on the page node as well as one on the scene below it. This is nice as it means I can handle drags on the scene without worrying if the user is dragging on a page – the scene will get the event regardless.
But it’s not so handy if you have something on top of the scene such as a navigation bar or zoom bar. In this case, we want the nav or zoom bar to handle the event rather than the scene. What I’d love here is some kind of event.kill() method that will stop the event from falling through to other nodes. Edit: Thanks to Andrew for pointing out in the comments that there is a method to achieve this – event.consume().
Despite this gotcha, my code is actually very tidy – partly because I have an additional requirement that I want to make my nav and zoom bar respond when you click in their area, and not just on their knobs. What I do here is to not put event handlers on the navigation and zoom nodes, instead of handling them on the scene events like so:
if (navBar.isNavBarDrag(mouseEvent)) {
// The nav bar is handling the drag.
} else if (zoomBar.isZoomBarDrag(mouseEvent)) {
// The zoom bar is handling the drag.
} else {
// Scene drag
}
The mouseEvent gets passed through to the nav and zoom bar objects so that it can check the x and y coordinates to see if they need to handle it. If they do, they return true to stop the scene from handling the event.
I hope you have found this article useful. What do you think of the way that JavaFX handles mouse events?
If you want to try out our new JavaFX PageFlow mode, you can download the 14 days free trial of JPedal.
Read my other articles I have written on converting our Java3D usage into JavaFX.