Category Archives: JavaFX

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.

Related Posts:

How to set Z order of nodes in JavaFX

One of the problems that cropped up while converting our Java3D PageFlow PDF viewer mode to JavaFX was the issue of how to replicate z positioning of Java3D’s 3D scene with JavaFX’s 2D scene.

Scaling the size is an easy way to make things appear further away, but there was no obvious way to set a z order to make one node appear in front of the other, mainly due to a distinct lack of a .setZ() or similar method.

The solution is that in JavaFX the Z order of Nodes is not controlled by a z position property of the nodes, it is actually controlled by the order in which they appear in the scene graph.

Put another way, the order that you add your elements to the scene matters. If you add image 1 then add image 2, as image 2 was the last thing to be added it will therefore be the last thing to be drawn, so image 2 will appear in front of image 1.

To demonstrate, here’s a video of our JavaFX PageFlow mode:

As you can see, the page in the center is closest to us, and as pages get further from the center, they also move backwards.

So to make all that appear in the correct order, the first thing to add to the scene is the 2 background colors. Next up are the pages. As the pages at the outer edges are furthest away they need to be added first. Rather than alternating between each side of the main page, as the pages either side of the main page will never overlap I can actually do one side at a time. Then the main page gets added, and lastly the controls like the nav and zoom bars.

Here’s a quick visual demonstration:

PageFlow Z order

In my code, this resulted in creation a reorder method whose job is specifically to pull out the pages in the scene graph, then put them back in the correct order.

Having this much control is definitely advantageous – it allows you to specifically optimize the sorting for your use case, but it’s also a little unclear. Perhaps it would be advantageous to have a simple setZ() method too. What do you think?

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.

Related Posts:

Announcing the PageFlow PDF Viewer Mode in JavaFX!

JavaFX has come a long way since it’s inception back in 2007. One of the main things that we took away with us from JavaOne2012 is that Oracle are investing heavily in JavaFX, to the point that it now has a powerful feature set and is a strong contender for our attention.

Knowing the future is bright, we decided it time to look further into how we can make use of JavaFX in our Java PDF Viewer. Our attention turned to our PageFlow mode currently written in Java3D. If you haven’t already, check out Sam’s excellent article on The Top 5 interesting things Java3D taught us.

Java3D does the job adequately – it’s stable, visually appealing, performance is excellent, and it does everything we need it to, but our main issue is support. Java3D is not widely used, and in order to use the mode, you need to head over to the Oracle website to download and install Java3D – it’s not ideal.

Since Java SE 7u6, JavaFX has been bundled with the Java SE platform,which means that support for JavaFX is good, and will only improve in the future. This makes it perfect for the PageFlow mode. Our only concern was if JavaFX could match the quality of a ground up built 3D technology like Java3D. I have created 2 videos below so you can see how we did!

Java3D:

JavaFX:

If you wish to use the new JavaFX PageFlow mode, it is now in our monthly release. If you are new to JPedal, it’s a fully featured Java PDF Library which you can learn more about here.

Don’t forget to check back here soon, as I’ll have a more detailed comparison between Java3D vs JavaFX!

Related Posts:

Top 5 interesting things Java 3D taught us

A few years ago, we completely overhauled our simple, static PageFlow mode and started from scratch with Java 3D. It’s served us well, but we think now is the time to move on to newer, better supported and more widespread technologies.

We thought it might be interesting to look back at our experience with Java 3D, and perhaps see how other technologies can learn from its successes and failures.

5. Swing really can’t do everything…

Here’s what PageFlow looked like when we wrote the first version in Swing:

4PageFlow

It’s completely static and has rough edges. Even this depends on the abandoned JAI libraries for Perspective transforms, which are pretty slow – you always see blank pages while the images load in.

And in Java 3D:

I think that pretty much tells the story.

 

4. …But you still need to deal with it

Even though we wrote PageFlow itself in Java 3D, it still had to slot into our Swing based viewer. That meant putting it inside a standard Swing LayoutManager with the hope that it’d just fit in and work.

Well, for the most part, it did – with the notable exception of the menu bar. No matter what you do, standard Swing menus will always hide behind a Canvas3D – the only workaround is to use the old Heavyweight menus.

ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);

Another nice feature is the ability to draw over the top of the 3D canvas using Swing’s Graphics2D interface. This works through overriding the postRender() method of the canvas. This was originally used to draw the navigation bar over the scene, until it became clear that this was the primary cause of some pretty major performance issues! Unfortunately this forced us to place the scrollbar in its own component below the main scene, where it does not affect performance in the slightest.

3. Developers expect the same kind of backwards compatibility as with the core libraries

One of the key features of PageFlow is being able to click on a page to make it central. You probably expect that this is simple.

Well, it’s not as simple as you might think. Rather than events going to the objects being rendered, all events go to the Canvas. You then use a process called ‘Picking’ to work out which object was under the cursor during the event.

Java 3D has always supported Picking, but 1.2 introduced a completely new (and, at least, much improved) API for picking, while deprecating those present in 1.1.

I found that similar situations were present across most versions of Java 3D, including differences between the Mac and PC versions, meaning I eventually had to lock it down to version post 1.31 on PC and 1.4 on Mac.

2. Scene graphs work

Admittedly ‘scene graph’ is a term with fuzzy edges – one could even argue that Swing’s system of nested layout managers could be considered a scene graph – there is no doubting that Java 3D uses the concept to great effect.

For example, a page is actually an object of type Box. The page needs to move, so we add it to a TransformGroup. However, we want a shiny reflection from the surface below - but Java 3D can’t do that, so we fake it.

We have a second box, the same as the first, but upside down – we created another TransformGroup, this one simply to flip the page upside down. We now add this whole new subtree which creates the reflection to our main TransformGroup which already holds the Page.

Now we have both a page and it’s reflection, ready to be transformed. Want the page to move left and right? Sure. Zoom in from the back of the scene? No problem. Repeat for the number of pages in the PDF you’re viewing, and we’re in business. It’s hard to imagine a clearer solution to the organisational problem of arranging visual elements than the scene graph.

 

1. If you’re not included in the main Java download, nobody will use you

Really, I find it hard to believe that any end user is likely to bother to install Java 3D – certainly not by downloading it from Oracle’s page. The obvious solution is to offer a bundle with both your application and Java 3D – but Java 3D is, unlike Java itself, platform dependent. This leaves creating separate bundles for each platform as your only option, which somehow just doesn’t feel right when working with Java.

 

So there we have it – have you played with Java 3D? What did you think?

And for those of you worried about our PageFlow mode disappearing, fear not – we already have something better waiting in the wings.

Related Posts:

Setting AffineTransform and Font in FXML vs JavaFX

JavaFX is well documented and has lots of examples in the API to help when writing your application, but as FXML is not Java there is unfortunately no such API and very few examples, which can make hand-coding FXML a little more tricky. I suspect the cause of this is that the number of use cases for hand coding FXML is low, and we are expected to use automated tools such as SceneBuilder.

IDEs are slowly catching up and NetBeans now offers FXML code completion to lend a hand, but unfortunately XML code completion is not as intuitive as for regular code, and it can be difficult to know what to start with, as I will show below.

Our use case is that we are converting PDF files into other formats, for example HTML5, SVG, JavaFX and FXML. As we are not doing this by hand, something like SceneBuilder is not an option – instead we must programmatically generate the HTML, SVG, JavaFX and FXML files.

As a baseline, here is the code to draw some text with an AffineTransform and Font set in JavaFX:

        Text text = new Text();
        text.setText("My Text!");
        text.setFill(Color.RED);
        text.getTransforms().add(Transform.affine(0.97, -0.19, 0.19, 0.97, 50, 50));
        text.setFont(Font.font("Arial", FontWeight.BOLD, FontPosture.ITALIC, 24));
        content.getChildren().add(text);

It is possible to do very similar in FXML, for example in the .FXML file you can have as little as this:

	<Text fx:id="text" />

Which you are then able to access in the Controller Java class by declaring the Text like so:

    @FXML private Text text;

And then modifying in the initialize (or other method) like so:

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        text.setText("My Text!");
        text.setFill(Color.RED);
        text.getTransforms().add(Transform.affine(0.97, -0.19, 0.19, 0.97, 50, 50));
        text.setFont(Font.font("Arial", FontWeight.BOLD, FontPosture.ITALIC, 24));
    }

This works well, but is unfortunately not without problems. One of the reasons I am a fan of using FXML over JavaFX is that if you generate a large FXML file (for example if you are converting PDF to FXML where you have no control over how much of what is in the PDF), there is no compiler that will refuse to compile your class because a method exceeds the 64KB size limit.

It is possible to split the methods up and chain them together, but this is messy, especially when you need to stop halfway through declaring something long like paths of a shape, and you need to pass all your fields into the next method to continue adding.

The best option is to get as much as possible into the FXML file. Here is that same piece of text with everything declared inside the FXML file:

	<Text text="My Text!" fill="#FF0000" fx:id="affineText">
		<font>
			<Font name="Arial Bold Italic" size="24" />
		</font>
		<transforms>
			<javafx.scene.transform.Affine mxx="0.97" myx="-0.19" mxy="0.19" myy="0.97" tx="50" ty="50" />
		</transforms>
	</Text>

If you create a new FXML project in NetBeans and add the above to Sample.fxml, you will hopefully see the same as this screenshot:

fxml

There are some noticeable differences with the FXML version. For example the fill is set with a hex color, and the font weight and posture is set in the same string as the font name. It is also possible to add fill tags, and set the color like so:

	<fill>
		<javafx.scene.paint.Color red="1" green="0" blue="0" />
	</fill>

It is important to note that things like the font and transform (and color) go inside their own tags within the Text tag, but in my simple example you will also see it’s possible to self close the tag.

In this example I have referenced the class in the tag for transform and color, but it is also possible to add imports for those classes like so, and use them without the class path:

	<?import javafx.scene.paint.*?>
	<?import javafx.scene.transform.*?>

Please do try out our PDF to JavaFX and FXML converter and let us know what you think. You can learn more about what else we do here. What are your thoughts on FXML?

Related Posts: