JavaFX Tip 3: Use Callback Interface

As a UI framework developer it is part of my job to provide ways to customize the appearance and behavior of my controls. In many cases this is done by allowing the framework user to register a factory on a control. In the past I would have created a factory interface for this and provided one or more default implementations within the framework.

These things are done differently in JavaFX and I have started to embrace it for my own work. JavaFX uses a generic interface called javafx.util.Callback wherever a piece of code is needed that produces a result (R) for a given parameter (P).

The interface looks like this:

public interface Callback<P,R> {
    public R call(P param);
}

Advantages

At first I didn’t like using this interface because my code was loosing verbosity: I no longer had self-explaining interface names. But in the end I realized that the advantages overweight the lack of verbosity. The advantages being:

  • We end up writing less code. No specialized interface, no default implementations.
  • The developer using the API does not have to remember different factories, instead he can focus on the object that he wants to create and the parameters that are available to him.
  • The Callback interface is a functional interface. We can use Lambda expressions, which makes the code more elegant and we once again have to write less code.

Case Study

The  FlexGanttFX framework contains a control called Dateline for displaying (surprise) dates. Each date is shown in its own cell. The dateline can display different temporal units (ChronoUnit from java.time, and SimpleUnit from FlexGanttFX). A factory approach is used to build the cells based on the temporal unit shown.

Before I was using the callback approach I had the following situation: an interface called DatelineCellFactory with exactly one method createDatelineCell(). I was providing two default implementations called ChronoUnitDatelineCellFactory and SimpleUnitDatelineCellFactory. By using Callback I was able to delete all three interfaces / classes and in the skin of the dateline I find the following two lines instead:

dateline.setCellFactory(SimpleUnit.class,
    unit -> new SimpleUnitDatelineCell());

dateline.setCellFactory(ChronoUnit.class,
    unit -> new ChronoUnitDatelineCell());

Two lines of code instead of three files! I think this example speaks for itself.

JavaFX Tip 2: Sharp Drawing with Canvas API

When I initially started out working with the Canvas API I noticed that the results of my rendering code were somewhat blurry and even worse, inconsistent. Some lines were blurry, others sharp. Coming from Swing it took me some time to realize that this was caused by the coordinate system of JavaFX, which allows for double precision rendering.
To solve this problem all that is needed is to use coordinates “in the middle”. So in my code you now find a lot of methods called snapXZY() (similar methods can be found in the JavaFX code itself), which first casts the given coordinate to an integer and then adds .5 to it. The following screenshot shows the difference when using this approach.

Bildschirmfoto 2014-04-10 um 11.59.49
The code below was used for this example:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * Tip 2: How to render sharp lines in a canvas.
 */
public class Tip2DrawingSharpLinesInCanvas extends 
        Application {

	class MyCanvas extends Canvas {

		public MyCanvas(boolean drawSharpLines) {

			setWidth(150);
			setHeight(150);

			double w = getWidth();
			double h = getHeight();

			GraphicsContext gc = getGraphicsContext2D();
			gc.clearRect(0, 0, w, h);

			gc.setStroke(Color.GRAY);
			gc.strokeRect(0, 0, w, h);

			for (double y = 20; y <= h - 20; y += 10) {
				if (drawSharpLines) {
					// Snap the y coordinate 
					gc.strokeLine(10, 
                                   snap(y), 
                                   w - 10, 
                                   snap(y));
				} else {
					gc.strokeLine(10, y, w - 10, y);
				}
			}
		}
		
		private double snap(double y) {
			return ((int) y) + .5;
		}
	}

	@Override
	public void start(Stage stage) throws Exception {
		MyCanvas canvasBlurry = new MyCanvas(false);
		MyCanvas canvasSharp = new MyCanvas(true);

		Label labelBlurry = new Label("Blurry");
		Label labelSharp = new Label("Sharp");

		VBox.setMargin(canvasBlurry, new Insets(10));
		VBox.setMargin(canvasSharp, new Insets(10));

		VBox.setMargin(labelBlurry, 
                     new Insets(10, 10, 0, 10));
		VBox.setMargin(labelSharp, 
                     new Insets(10, 10, 0, 10));

		VBox box = new VBox();
		box.getChildren().add(labelBlurry);
		box.getChildren().add(canvasBlurry);
		box.getChildren().add(labelSharp);
		box.getChildren().add(canvasSharp);

		stage.setScene(new Scene(box));
		stage.setTitle("Tip 2: Sharp Lines in Canvas");
		stage.show();
	}

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

JavaFX Tip 1: Resizable Canvas

While working on FlexGanttFX I had to deal a lot with the JavaFX Canvas node. I am using it to render activities on a timeline. Each row in the Gantt chart is a Canvas node. The user has the option to resize each row individually. So I had to figure out the best way to resize a canvas, which out-of-the-box is not resizable. The listing below shows how this can be accomplished.

The main steps needed are:

  • Create a subclass of Canvas.
  • Override the isResizable() method and return true.
  • Override the prefWidth() and prefHeight() methods. Return the values of Canvas.getWidth() and Canvas.getHeight().
  • Add listeners to the width and height properties of Canvas in order to trigger a redraw when the size of the canvas changes.
  • Bind the width and height properties of Canvas to the width and height properties of the parent pane.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * Tip 1: A canvas resizing itself to the size of
 *        the parent pane.
 */
public class Tip1ResizableCanvas extends Application {

	class ResizableCanvas extends Canvas {

		public ResizableCanvas() {
			// Redraw canvas when size changes.
			widthProperty().addListener(evt -> draw());
			heightProperty().addListener(evt -> draw());
		}

		private void draw() {
			double width = getWidth();
			double height = getHeight();

			GraphicsContext gc = getGraphicsContext2D();
			gc.clearRect(0, 0, width, height);

			gc.setStroke(Color.RED);
			gc.strokeLine(0, 0, width, height);
			gc.strokeLine(0, height, width, 0);
		}

		@Override
		public boolean isResizable() {
			return true;
		}

		@Override
		public double prefWidth(double height) {
			return getWidth();
		}

		@Override
		public double prefHeight(double width) {
			return getHeight();
		}
	}

	@Override
	public void start(Stage stage) throws Exception {
		ResizableCanvas canvas = new ResizableCanvas();

		StackPane stackPane = new StackPane();
		stackPane.getChildren().add(canvas);

		// Bind canvas size to stack pane size.
		canvas.widthProperty().bind(
                       stackPane.widthProperty());
		canvas.heightProperty().bind(
                       stackPane.heightProperty());

		stage.setScene(new Scene(stackPane));
		stage.setTitle("Tip 1: Resizable Canvas");
		stage.show();
	}

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

When run you should see the following:
Bildschirmfoto 2014-04-10 um 11.30.31

Finally: Agenda Visualization in FlexGanttFX

Probably a small step for mankind but a giant one for FlexGanttFX. I finished a first working version of actual agenda style visualization of Gantt chart data including editing capabilities. What really made a difference compared to previous attempts in the Swing FlexGantt is the new java.time API. LocalDate and LocalTime came in very handy and make the computation of x and y coordinates a piece of cake. You can see the new agenda layout in the video below.

The data in this example is fake, so you will not see any updates to the capacity profiles after changes to the agenda entries.

Something to hide? You need HiddenSidesPane

One of my Gantt chart users wanted to use as much real estate on the screen as possible and asked if the scrollbars could be removed. But how do you navigate without scrollbars? Ok, there are all kinds of keyboard shortcuts and of course the usual mouse drag already supported by FlexGanttFX, but a visual control like a scrollbar is something most users would still expect to see these days (at least on desktops).

So here is the solution: I used to have a manager who when asked “do you want me to do this or that?” would always reply with “both!”. So I followed in his footsteps and implemented scrollbars that only appear when you need them and in order to support this I had to write another control for the ControlsFX project. The control is called HiddenSidesPane and supports four (initially hidden) side nodes. These nodes become visible when the user moves the mouse cursor close to the edges of the primary content node. They will slide-in with a little animation and slide-out when the mouse cursor leaves them. A side node can also be pinned, so that it stays visible.

The video below shows the control in action. I have commited it to the ControlsFX repository today and hope that it will be included in the 8.0.5 release.

The second video shows how the pane is used within FlexGanttFX.

And another one: MasterDetailsPane

I finished another JavaFX control today, which I urgently needed for my FlexGanttFX control: a master details pane, which allows the user to show / hide a node with detailed information for a so-called “master” node. My use case is a dual Gantt chart control where a primary (master) Gantt chart is initially shown and then a secondary (details) Gantt chart can be added to the window (stage). By using the MasterDetailsPane the user can easily toggle the visibility of the second Gantt chart at the bottom. At the same time each Gantt chart also has a property sheet (ControlsFX) attached to it, which can be made visible on its right side. So I end up with two nested MasterDetailsPane instances. The video below shows the control in action.

New JavaFX Control PlusMinusAdjuster

Today I had once again the pleasure to write a small and highly specialized control for JavaFX, which might be useful for others as well. I am calling it PlusMinusAdjuster and all it does is firing value events with values ranging from -1 to +1. The difference to a normal slider is that it continues to fire events even when the value has not changed. This kind of behavior is useful for implementing scrolling through large data sets. A normal scrollbar often causes big jumps even when the user only moves it a few pixels. For me this was the case for my Gantt chart component when the timeline horizon was very large and the currently visible time span was small.

So this is what I came up with:

Image

I have asked the ControlsFX guys if they are interested in including this control. The initial response was positive. I will keep you updated.

FlexGantt for JavaFX 8 = FlexGanttFX

For the last 6 months I had the opportunity to port my FlexGantt Gantt charting framework for Swing to JavaFX 8. The concepts behind JavaFX are very different to Swing so the initial ramp-up phase was longer than I wanted it to be. However, in the meantime I feel confident that I am on the right track with my approach to Gantt charting with this new technology.

The following videos show the current state of my development efforts.

Project Planning

The first video shows one of the most common use cases for Gantt charts: the display of a project schedule. This demo uses the open source project MPXJ to read in an original MSProject file.

Aircraft Scheduling

The second video shows how the user can still nicely interact with the display even when working with hundreds of activities at the same time. In this case the data file used represents flights of aircrafts.

Course Scheduling

The next video shows how FlexGanttFX can be used for course scheduling. The user can resize a row to make the individual sessions of a training week visible. The sessions can be rescheduled by simple drag gestures.

Styling

JavaFX is all about CSS-based styling. The test application in the video below shows several versions of the timeline control of FlexGanttFX. Each version has its own stylesheet attached to it. All timeline controls use the same model in the background, which makes them scroll in synch.

Split Screen

A feature often asked for in the Gantt chart domain is the ability to split the Gantt chart. This way the user can work on two related data models at the same time (e.g. aircrafts and crews) or look at two different regions within the same data model.

I am currently planning to start an early adopter program around March 1st 2014. If you are interested in participating then please fill out the form below.