MouseEvents in JavaFX

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 center to that page.

My first thought was that this would require 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, 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 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 free 30 day free trial of JPedal here.

I have written several articles on converting our Java3D usage into JavaFX and you can read the other articles here.

Ebook Page Link

The following two tabs change content below.
Leon is a Developer at IDRsolutions, focusing mainly on development of the PDF to HTML5/SVG converter. He was a speaker at JavaOne 2012, co-presenting a session titled 'Lessons Learned in Writing a PDF-to-JavaFX Converter for NetBeans'.

Related Posts:

Leon Atherton

About Leon Atherton

Leon is a Developer at IDRsolutions, focusing mainly on development of the PDF to HTML5/SVG converter. He was a speaker at JavaOne 2012, co-presenting a session titled 'Lessons Learned in Writing a PDF-to-JavaFX Converter for NetBeans'.

4 thoughts on “MouseEvents in JavaFX

  1. Andrew Bouis

    Thanks, got me looking over the event api.

    Does event.consume() match the “event.kill” you’re looking for, or is there a difference?
    http://docs.oracle.com/javafx/2/api/javafx/event/Event.html#consume%28%29

    • Thanks for commenting Andrew. I have just tested and you are correct! I have updated the article.

      Personally, I prefer my method name suggestion. .arm() and .disarm() are personal favourites from the ButtonBase class.

      An extra tip for those reading the comments that I forgot to add to the article: you can detect double clicks using getClickCount() like so: if (mouseEvent.getClickCount() == 2) { // Double click }

  2. vid

    Thank you so much!!
    I didn’t know why On Mouse Releas wasn’t firing and after an hour of troubleshooting I came to this blog, which solved my problem in a second :).

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>