JavaFX Tip 18: Path Clipping

I recently noticed that the PopOver control, which I committed to the ControlsFX project, does not properly clip its content. It became obvious when I was working on the accordion popover for the FlexCalendarFX framework. Whenever the last titled pane was expanded the bottom corners were no longer rounded but square. After placing a red rectangle as content to the titled pane it became clear to me that I forgot to add clipping. The following picture shows the problem.Bildschirmfoto 2015-02-18 um 12.20.09

Normally clipping in JavaFX is quite easy. All it takes is an additional node and a call to setClip(node). However, normally this clip is a simple shape, like a rectangle. In the PopOver case the clip had to be a path, just like the original path that was used for the shape of the PopOver. Why a path? Because the popover, when “attached” to an owner, also features an arrow pointing at the owner. See screenshot below.

Bildschirmfoto 2015-02-18 um 12.38.53

So the good thing was that the original path gets constructed based on a list of path elements. These are not nodes and can be reused for a second path. When I tried this the result was a PopOver that only consisted of a border with no content at all.

Bildschirmfoto 2015-02-18 um 12.25.23

The reason for this was the fact that the path was not filled. Once I set a fill on the clip path the result was what I was aiming for.

Bildschirmfoto 2015-02-18 um 12.25.40

Now the PopOver control clipped its content correctly. The image below shows the final result.

Bildschirmfoto 2015-02-18 um 12.19.55

Some might say that this is just a minor detail and they are right, but it is this attention to detail that makes an application stand out and look professional.

The image below shows how the PopOver is used within FlexCalendarFX.

Bildschirmfoto 2015-02-17 um 15.00.39

Advertisement

2015: The Year When JavaFX Takes Over

trend

 

I must say that I very much enjoy using Google trends to evaluate the importance of a technology and to see what its future will look like. So today I ran a comparison between “Java Swing” and “JavaFX”. The result shows two things: once we enter the next year JavaFX will be more relevant than Swing but JavaFX will not be that relevant either.

I think this is totally wrong.

Based on the feedback I am getting from the community and also on the projects that I had contact with I foresee a rapid increase in JavaFX usage (and Google searches) over the next 6 to 9 months.

The main reason is the fact that a lot of companies will finally upgrade to Java 8 next year, and JavaFX has really only reached its full potential with this latest Java release. The second reason is the increased availability of third party solutions: open source projects like ControlsFX and JFXtras, and some commercial products (like mine: FlexGanttFX and soon FlexCalendarFX). The third reason will be the availability of JavaFX 8 on mobile devices (Android and iOS). Once managers see that they can ask their developers to use a single UI toolkit for desktop and mobile  development there will be no holding back.

So that’s my prognosis, please make sure to check back in Q2 2015 to see if I was right or wrong.

New Custom Control: TaskProgressView

I have written a new custom control and commited it to the ControlsFX project. It is a highly specialized control for showing a list of background tasks, their current status and progress. This is actually the first control I have written for ControlsFX just for the fun of it, meaning I do not have a use case for it myself (but sure one will come eventually). The screenshot below shows the control in action.

task-monitor

If you are already familiar with the javafx.concurrent.Task class you will quickly grasp that the control shows the value of its title, message, and progress properties. But it also shows an icon, which is not covered by the Task API. I have added an optional graphics factory (a callback) that will be invoked for each task to lookup a graphic node that will be placed on the left-hand side of the list view cell that represents the task.

A video showing the control in action can be found here:

The Control

Since this control is rather simple I figured it would make sense to post the entire source code for it so that it can be used for others to study. The following listing shows the code of the control itself. As expected it extends the Control class and provides an observable list for the monitored tasks and an object property for the graphics factory (the callback).

package org.controlsfx.control;

import impl.org.controlsfx.skin.TaskProgressViewSkin;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.util.Callback;

/**
 * The task progress view is used to visualize the progress of long running
 * tasks. These tasks are created via the {@link Task} class. This view
 * manages a list of such tasks and displays each one of them with their
 * name, progress, and update messages.<p>
 * An optional graphic factory can be set to place a graphic in each row.
 * This allows the user to more easily distinguish between different types
 * of tasks.
 *
 * <h3>Screenshots</h3>
 * The picture below shows the default appearance of the task progress view
 * control:
 * <center><img src="task-monitor.png" /></center>
 *
 * <h3>Code Sample</h3>
 *
 * <pre>
 * TaskProgressView&lt;MyTask&gt; view = new TaskProgressView&lt;&gt;();
 * view.setGraphicFactory(task -> return new ImageView("db-access.png"));
 * view.getTasks().add(new MyTask());
 * </pre>
 */
public class TaskProgressView<T extends Task<?>> extends Control {

    /**
     * Constructs a new task progress view.
     */
    public TaskProgressView() {
        getStyleClass().add("task-progress-view");

        EventHandler<WorkerStateEvent> taskHandler = evt -> {
            if (evt.getEventType().equals(
                    WorkerStateEvent.WORKER_STATE_SUCCEEDED)
                    || evt.getEventType().equals(
                            WorkerStateEvent.WORKER_STATE_CANCELLED)
                    || evt.getEventType().equals(
                            WorkerStateEvent.WORKER_STATE_FAILED)) {
                getTasks().remove(evt.getSource());
            }
        };

        getTasks().addListener(new ListChangeListener<Task<?>>() {
            @Override
            public void onChanged(Change<? extends Task<?>> c) {
                while (c.next()) {
                    if (c.wasAdded()) {
                        for (Task<?> task : c.getAddedSubList()) {
                            task.addEventHandler(WorkerStateEvent.ANY,
                                    taskHandler);
                        }
                    } else if (c.wasRemoved()) {
                        for (Task<?> task : c.getAddedSubList()) {
                            task.removeEventHandler(WorkerStateEvent.ANY,
                                    taskHandler);
                        }
                    }
                }
            }
        });
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new TaskProgressViewSkin<>(this);
    }

    private final ObservableList<T> tasks = FXCollections
            .observableArrayList();

    /**
     * Returns the list of tasks currently monitored by this view.
     *
     * @return the monitored tasks
     */
    public final ObservableList<T> getTasks() {
        return tasks;
    }

    private ObjectProperty<Callback<T, Node>> graphicFactory;

    /**
     * Returns the property used to store an optional callback for creating
     * custom graphics for each task.
     *
     * @return the graphic factory property
     */
    public final ObjectProperty<Callback<T, Node>> graphicFactoryProperty() {
        if (graphicFactory == null) {
            graphicFactory = new SimpleObjectProperty<Callback<T, Node>>(
                    this, "graphicFactory");
        }

        return graphicFactory;
    }

    /**
     * Returns the value of {@link #graphicFactoryProperty()}.
     *
     * @return the optional graphic factory
     */
    public final Callback<T, Node> getGraphicFactory() {
        return graphicFactory == null ? null : graphicFactory.get();
    }

    /**
     * Sets the value of {@link #graphicFactoryProperty()}.
     *
     * @param factory an optional graphic factory
     */
    public final void setGraphicFactory(Callback<T, Node> factory) {
        graphicFactoryProperty().set(factory);
    }

The Skin

As you might have expected the skin is using a ListView with a custom cell factory  to display the tasks.

package impl.org.controlsfx.skin;

import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;

import org.controlsfx.control.TaskProgressView;

import com.sun.javafx.css.StyleManager;

public class TaskProgressViewSkin<T extends Task<?>> extends
        SkinBase<TaskProgressView<T>> {

    static {
        StyleManager.getInstance().addUserAgentStylesheet(
                TaskProgressView.class
                        .getResource("taskprogressview.css").toExternalForm()); //$NON-NLS-1$
    }

    public TaskProgressViewSkin(TaskProgressView<T> monitor) {
        super(monitor);

        BorderPane borderPane = new BorderPane();
        borderPane.getStyleClass().add("box");

        // list view
        ListView<T> listView = new ListView<>();
        listView.setPrefSize(500, 400);
        listView.setPlaceholder(new Label("No tasks running"));
        listView.setCellFactory(param -> new TaskCell());
        listView.setFocusTraversable(false);

        Bindings.bindContent(listView.getItems(), monitor.getTasks());
        borderPane.setCenter(listView);

        getChildren().add(listView);
    }

    class TaskCell extends ListCell<T> {
        private ProgressBar progressBar;
        private Label titleText;
        private Label messageText;
        private Button cancelButton;

        private T task;
        private BorderPane borderPane;

        public TaskCell() {
            titleText = new Label();
            titleText.getStyleClass().add("task-title");

            messageText = new Label();
            messageText.getStyleClass().add("task-message");

            progressBar = new ProgressBar();
            progressBar.setMaxWidth(Double.MAX_VALUE);
            progressBar.setMaxHeight(8);
            progressBar.getStyleClass().add("task-progress-bar");

            cancelButton = new Button("Cancel");
            cancelButton.getStyleClass().add("task-cancel-button");
            cancelButton.setTooltip(new Tooltip("Cancel Task"));
            cancelButton.setOnAction(evt -> {
                if (task != null) {
                    task.cancel();
                }
            });

            VBox vbox = new VBox();
            vbox.setSpacing(4);
            vbox.getChildren().add(titleText);
            vbox.getChildren().add(progressBar);
            vbox.getChildren().add(messageText);

            BorderPane.setAlignment(cancelButton, Pos.CENTER);
            BorderPane.setMargin(cancelButton, new Insets(0, 0, 0, 4));

            borderPane = new BorderPane();
            borderPane.setCenter(vbox);
            borderPane.setRight(cancelButton);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        }

        @Override
        public void updateIndex(int index) {
            super.updateIndex(index);

            /*
             * I have no idea why this is necessary but it won't work without
             * it. Shouldn't the updateItem method be enough?
             */
            if (index == -1) {
                setGraphic(null);
                getStyleClass().setAll("task-list-cell-empty");
            }
        }

        @Override
        protected void updateItem(T task, boolean empty) {
            super.updateItem(task, empty);

            this.task = task;

            if (empty || task == null) {
                getStyleClass().setAll("task-list-cell-empty");
                setGraphic(null);
            } else if (task != null) {
                getStyleClass().setAll("task-list-cell");
                progressBar.progressProperty().bind(task.progressProperty());
                titleText.textProperty().bind(task.titleProperty());
                messageText.textProperty().bind(task.messageProperty());
                cancelButton.disableProperty().bind(
                        Bindings.not(task.runningProperty()));

                Callback<T, Node> factory = getSkinnable().getGraphicFactory();
                if (factory != null) {
                    Node graphic = factory.call(task);
                    if (graphic != null) {
                        BorderPane.setAlignment(graphic, Pos.CENTER);
                        BorderPane.setMargin(graphic, new Insets(0, 4, 0, 0));
                        borderPane.setLeft(graphic);
                    }
                } else {
                	/*
                	 * Really needed. The application might have used a graphic
                	 * factory before and then disabled it. In this case the border
                	 * pane might still have an old graphic in the left position.
                	 */
                	borderPane.setLeft(null);
                }

                setGraphic(borderPane);
            }
        }
    }
}

The CSS

The stylesheet below makes sure we use a bold font for the task title, a smaller / thinner progress bar (without rounded corners), and list cells with a fade-in / fade-out divider line in their bottom position.

.task-progress-view  {
       -fx-background-color: white;
}

.task-progress-view > * > .label {
 	-fx-text-fill: gray;
 	-fx-font-size: 18.0;
 	-fx-alignment: center;
 	-fx-padding: 10.0 0.0 5.0 0.0;
}

.task-progress-view > * > .list-view  {
 	-fx-border-color: transparent;
 	-fx-background-color: transparent;
}

.task-title {
	-fx-font-weight: bold;
}

.task-progress-bar .bar {
	-fx-padding: 6px;
	-fx-background-radius: 0;
	-fx-border-radius: 0;
}

.task-progress-bar .track {
	-fx-background-radius: 0;
}

.task-message {
}

.task-list-cell {
    -fx-background-color: transparent;
    -fx-padding: 4 10 8 10;
    -fx-border-color: transparent transparent linear-gradient(from 0.0% 0.0% to 100.0% 100.0%, transparent, rgba(0.0,0.0,0.0,0.2), transparent) transparent;
}

.task-list-cell-empty {
	-fx-background-color: transparent;
    -fx-border-color: transparent;
}

.task-cancel-button {
	-fx-base: red;
	-fx-font-size: .75em;
	-fx-font-weight: bold;
	-fx-padding: 4px;
	-fx-border-radius: 0;
	-fx-background-radius: 0;
}

New Release: FlexGanttFX 1.0.0

Gantt Chart
I finally managed to put together a 1.0.0 production-ready release of FlexGanttFX. After two early access releases at the beginning of the year I put in a lot of hours to get the framework to a maturity level that I deem to be high enough for real-world application development. The release can be downloaded from the product website at http://www.flexganttfx.com. I would recommend to run the three standalone demos in this distribution to get an idea of FlexGanttFX’s capabilities.

FlexGanttFX is much bigger than a regular JavaFX control. The view consists of 83 and the model of 58 classes. It could have been even bigger but during development I decided to contribute some of the custom controls to the ControlsFX project (e.g. StatusBar, PopOver, PlusMinusSlider, HiddenSidesPane, MasterDetailPane).

emirates

There were two events in the past that delayed the release: the first one was a requirement from an early adopter to use the graphics area of the Gantt chart standalone, without the tree table view on the left-hand side. They wanted to use it as an editor for creating classroom schedules. Even though this requirement delayed the release it turned out to be very beneficial for the overall design of the framework. In the end I came up with four different ways the graphics can be presented: single row, using a VBox, using a SplitPane, or using a ListView.

The second event was the release of JDK 8u20. Changes were made to the way CSS was applied / processed which caused many warning messages to be shown in the console (“…unable to resolve -fx-background-color …”). While most people only get these warnings I could actually see that the layout of the controls in the Gantt chart was messed up. I had to find work-arounds for these issues which took a long time. I still see these messages but they seem to be harmless now. If anyone can point me to the root of this problem I would very much appreciate it.

msproject

I would like to say a special thank you to Gilberto Gomez, Werner Lehmann, and Christian Hollmann of MINT Media who provided very valuable input during the development of FlexGanttFX. I would also like to say thank you to the entire JavaFX developer community, especially the guys from Oracle and ControlsFX (Jonathan Giles, Eugene Ryzhikov).

JavaFX Tip 10: Custom Composite Controls

Writing custom controls in JavaFX is a simple and straight forward process. A control class is needed for controlling the state of the control (hence the name). A skin class is needed for the apperance of the control. And more often than not a CSS file for customizing the apperance.

A common approach for controls is to hide the nodes they are using inside their skin class. The TextField control for example uses two instances of javafx.scene.text.Text. One for the regular text, one for the prompt text. These nodes are not accessible via the TextField API. If you want to get a reference to them you would need to call the lookup(String) method on Node. So far so good. It is actually hard to think of use cases where you would actually need access to the Text nodes.

But…

It becomes a whole different story if you develop complex custom controls. The FlexGanttFX Gantt charting framework is one example. The GanttChart control consists of many other complex controls and following the “separation of concerns” principle these controls carry all those methods and properties that are relevant for them to work properly. If these controls were hidden inside the skin of the Gantt chart then there would be no way to access them and the Gantt chart control would need to implement a whole buch of delegation methods. This would completely clutter the Gantt chart API. For this reason the GanttChart class does provide accessor methods to its child controls and even factory methods for creating the child nodes.

Example

The following screenshot shows a new control I am currently working on for the ControlsFX project. I am calling it ListSelectionView and it features two ListView instances. The user can move items from one list to another by either double clicking on them or by using the buttons in the middle. 

ListSelectionView 

List views are complex controls. They have their own data and selection models, their own cell factories, they fire events, and so on and so on. All of these things we might want to either customize or listen to. Something hard to do if the views are hidden in the skin class. The solution is to create the list views inside the control class via protected factory methods and to provide accessor methods.

The following code fragment shows the pattern that can be used:

public class ListSelectionView<T> extends Control {

    private ListView<T> sourceListView;
    private ListView<T> targetListView;

    public ListSelectionView() {
        sourceListView = createSourceListView();
        targetListView = createTargetListView();
    }

    protected ListView<T> createSourceListView() {
        return new ListView<>();
    }

    protected ListView<T> createTargetListView() {
        return new ListView<>();
    }

    public final ListView<T> getSourceListView() {
        return sourceListView;
    }

    public final ListView<T> getTargetListView() {
        return targetListView;
    }
}

The factory methods can be used to create standard ListView instances and configure them right there or to return already existing ListView specializations. A company called ACME might already provide a standard set of controls (that implement the company’s marketing concept). Then the factory methods might return a control called ACMEListView.

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.

A task monitor popover

My JavaFX developer life currently has a strong focus on the popover control I wrote and contributed to the ControlsFX project. I now find use cases for it all over the place. The latest addition is a task monitor control for the JavaFX version of FlexGantt. The monitor lists the background tasks that are running to load the Gantt chart data for each row.Image

Once again I was inspired by Apple. This time by the “downloads” popover of Safari, which looks like this.

Image

Please note the fancy gray divider lines 🙂