Posts by Dirk Lemmermann

I am a Java Freelancer and Framework Developer located in Zurich, Switzerland. My primary focus is on UI development with JavaFX 8.

New kid on the blog: FormsFX

I am very happy to announce the immediate availability of FormsFX, a framework for (surprise!) creating forms in JavaFX. FormsFX is the result of a student project at the “Fachhochschule Nordwestschweiz” (FHNW) in Switzerland. The project was initiated and sponsored by me. I believe that the result of this project is of high quality and that other projects should be able to benefit from it, hence I am contributing it as an open source project to the Java community today.

The source code and documentation can be found on GitHub at this location: https://github.com/dlemmermann/formsfx

A binary distribution (JAR file) can be downloaded from my website.

Credits

The design and the implementation were done by Sacha Schmid and Rinesch Murugathas as part of their studies for their bachelor degree.

View, Model View, Model

The form renderer of FormsFX will automatically create a form for you based on a view model. The view model defines bindings to the properties of the domain model / business model. The renderer utilises a twelve column layout (similar to bootstrap). Labels and their fields can be placed on one or more of those columns. Each field defines its own column span. If there is not enough space left for a field then it will be automatically placed on the next row. The screenshot below shows you the form created by the demo application that ships with FormsFX.

 

Fluent API

The fluent API of FormsFX allows you to create a view model for a form that consists of several groups and sections. A group and a section can consist of several fields. The difference between a group and a section is simply the additional title property and the fact that sections can be expanded / collapsed. The code below shows how the first three fields of the screenshot above were defined.

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

Form form = Form.of(
        Group.of(
                Field.ofStringType(country.nameProperty())
                     .label("country_label")
                     .placeholder("country_placeholder")
                     .required("required_error_message")
                     .validate(StringLengthValidator.atLeast(2, "country_error_message")),
                Field.ofStringType(country.isoProperty())
                     .label("ISO_3166_label")
                     .placeholder("ISO_3166_placeholder")
                     .required("required_error_message")
                     .validate(StringLengthValidator.exactly(2, "ISO_3166_error_message")),
                Field.ofBooleanType(country.independenceProperty())
                     .label("independent_label")
                     .required("required_error_message")
        )).title("My Form").i18n(resourceBundle);

Fields

The following things can be defined for each field:

  • the type (string, number, boolean, ….)
  • a label
  • a placeholder text
  • whether the field is required
  • one or more validators
  • a tooltip
  • a column span
  • a format (e.g. decimal numbers)
  • a renderer (normally default used, e.g. textfield for text)
  • single- or multiline
  • editable or not
  • CSS style classes
  • an ID

i18n

Any form can also be associated with a resource bundle. This allows the application to switch field labels on the fly.

So please take FormsFX for a spin and let us know what you think.

Happy coding everyone![/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

JavaFX Tip 28: Pretty List View

When I look at the list views on my mobile phone I always notice that they display their scrollbar (normally a vertical one) very differently than JavaFX does. The same is true for applications running on MacOS X. Below you can see a snapshot of Apple’s calendar app. You will notice that the scrollbar is actually on top of the content.

In JavaFX the scrollbar would be placed to the right of the view. However on mobile devices in order to save space the scrollbar shows up on top of the content.

So obviously I wanted to see how I could achieve the same thing for my JavaFX apps. The trick to do this is to first “style away” the original scrollbars. Then you add your own scrollbar and lay it out in such a way that it will be on top of the content (the ListView). Next you style your own scrollbar to look nice and lightweight, and finally you bind your scrollbar to the original scrollbar.

In the end the result will look like this:

Below you can see the class PrettyListView, which extends a standard ListView.

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;

import java.util.Set;

public class PrettyListView<T> extends ListView<T> {

    private ScrollBar vBar = new ScrollBar();
    private ScrollBar hBar = new ScrollBar();

    public PrettyListView() {
        super();

        skinProperty().addListener(it -> {
            // first bind, then add new scrollbars, otherwise the new bars will be found
            bindScrollBars();
            getChildren().addAll(vBar, hBar);
        });

        getStyleClass().add("pretty-list-view");

        vBar.setManaged(false);
        vBar.setOrientation(Orientation.VERTICAL);
        vBar.getStyleClass().add("pretty-scroll-bar");
        vBar.visibleProperty().bind(vBar.visibleAmountProperty().isNotEqualTo(0));

        hBar.setManaged(false);
        hBar.setOrientation(Orientation.HORIZONTAL);
        hBar.getStyleClass().add("pretty-scroll-bar");
        hBar.visibleProperty().bind(hBar.visibleAmountProperty().isNotEqualTo(0));
    }

    private void bindScrollBars() {
        final Set<Node> nodes = lookupAll("VirtualScrollBar");
        for (Node node : nodes) {
            if (node instanceof ScrollBar) {
                ScrollBar bar = (ScrollBar) node;
                if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                    bindScrollBars(vBar, bar);
                } else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
                    bindScrollBars(hBar, bar);
                }
            }
        }
    }

    private void bindScrollBars(ScrollBar scrollBarA, ScrollBar scrollBarB) {
        scrollBarA.valueProperty().bindBidirectional(scrollBarB.valueProperty());
        scrollBarA.minProperty().bindBidirectional(scrollBarB.minProperty());
        scrollBarA.maxProperty().bindBidirectional(scrollBarB.maxProperty());
        scrollBarA.visibleAmountProperty().bindBidirectional(scrollBarB.visibleAmountProperty());
        scrollBarA.unitIncrementProperty().bindBidirectional(scrollBarB.unitIncrementProperty());
        scrollBarA.blockIncrementProperty().bindBidirectional(scrollBarB.blockIncrementProperty());
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        Insets insets = getInsets();
        double w = getWidth();
        double h = getHeight();
        final double prefWidth = vBar.prefWidth(-1);
        vBar.resizeRelocate(w - prefWidth - insets.getRight(), insets.getTop(), prefWidth, h - insets.getTop() - insets.getBottom());

        final double prefHeight = hBar.prefHeight(-1);
        hBar.resizeRelocate(insets.getLeft(), h - prefHeight - insets.getBottom(), w - insets.getLeft() - insets.getRight(), prefHeight);
    }
}

The CSS looks like this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

.pretty-list-view > .virtual-flow > .scroll-bar,
.pretty-list-view > .virtual-flow > .scroll-bar .decrement-arrow,
.pretty-list-view > .virtual-flow > .scroll-bar .increment-arrow,
.pretty-list-view > .virtual-flow > .scroll-bar .decrement-button,
.pretty-list-view > .virtual-flow > .scroll-bar .increment-button {
    -fx-pref-width: 0;
    -fx-pref-height: 0;
}

.pretty-list-view .pretty-scroll-bar:vertical .thumb {
    -fx-background-insets: 0 2 0 0;
}

.pretty-list-view .pretty-scroll-bar:horizontal .thumb {
    -fx-background-insets: 0 0 2 0;
}

.pretty-list-view .pretty-scroll-bar .decrement-arrow,
.pretty-list-view .pretty-scroll-bar .increment-arrow {
    -fx-pref-width: 0;
    -fx-pref-height: 0;
}

.pretty-list-view .pretty-scroll-bar {
    -fx-background-color: transparent;
    -fx-pref-width: 12;
    -fx-pref-height: 12;
    -fx-padding: 2;
}

.pretty-list-view .pretty-scroll-bar .thumb {
    -fx-background-color: rgba(0, 0, 0, .2);
    -fx-background-radius: 1000;
    -fx-pref-width: 12;
    -fx-pref-height: 12;
}

[/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

JavaFX Tip 27: HiRes / Retina Icons

I recently had a lot of icons sent to me by a graphics / UX designer. It was my job to add them to a JavaFX application. Each icon was shipped in three sizes (16×16, 32×32, 64×64). The naming convention for them was like this:

  • icon-name.png
  • icon-name@2x.png
  • icon-name@3x.png

At first I thought that this was some kind of standard naming used by graphic designers for their files but in the end it turns out to be a convention that was initially introduced by Apple for their Retina displays and adapted by the JavaFX team. The idea is that JavaFX will automatically load the appropriate image file for the current screen resolution. So when the application wants to load a 16×16 icon and JavaFX determines that the screen is a Retina / HiRes display it will then load the @2x file. The result of this are crisp graphics that take full advantage of these beautiful screens.

To enable this automatic image loading one can use CSS or code. In CSS you can define a style like this (assuming image file is in the same directory as CSS file):

    .my-icon {
        -fx-graphic: url("icon-name.png");
    }

Then you can apply the style to your button and it will show the @2x icon if applicable:

    button.getStyleClass().add("my-icon");

When you want to load images in code you have to make sure not to use input streams but a URL. Example:

    Image image = new Image(MyApp.class.getResource("icon-name.png").toExternalForm());

Using hires icons is really worth your time as your UI will look even more beautiful on HiRes displays.

JavaFX Tip 26: Go Dark – The “Darcula” Theme

I am a big fan of IntelliJ IDEA and use it every day for the various projects I work on. One thing I like a lot is the dark theme called “Darcula”. I like it because it looks cool and because of the low contrast it is better for my eyes. When you stare at something for at least eight hours a day for decades (yes, I am that old) then you better make sure it goes easy on your eyes. This said I obviously also like to use dark themes for my own creations. The following snapshot shows an application called “Presentation Builder”. It will be used by sports coaches to assemble presentation videos for their teams to discuss previous matches or to study the next opponent.

Another project I worked on is called “AnimakerFX” (working title). This is a side-project I like to work on when I have time (which unfortunately currently is not the case). Here I also used the dark theme. In general I think the dark theme goes well with applications that are used to produce visually rich content. The dark theme makes sure that the user’s focus remains on the content.

So how can we do this in JavaFX? By simply setting a new value in CSS for the “-fx-base” variable. This variable is part of the default modena.css stylesheet and it is the “base” for various other color constants. Below you can see how -fx-base is defined in Modena and how other colors are derived from it:

.root {
    /***************************************************************************
     *                                                                         *
     * The main color palette from which the rest of the colors are derived.   *
     *                                                                         *
     **************************************************************************/

    /* A light grey that is the base color for objects.  Instead of using
     * -fx-base directly, the sections in this file will typically use -fx-color.
     */
    -fx-base: #ececec;

    /* A very light grey used for the background of windows.  See also
     * -fx-text-background-color, which should be used as the -fx-text-fill
     * value for text painted on top of backgrounds colored with -fx-background.
     */
    -fx-background: derive(-fx-base,26.4%);

    /* Used for the inside of text boxes, password boxes, lists, trees, and
     * tables.  See also -fx-text-inner-color, which should be used as the
     * -fx-text-fill value for text painted on top of backgrounds colored
     * with -fx-control-inner-background.
     */
    -fx-control-inner-background: derive(-fx-base,80%);
    /* Version of -fx-control-inner-background for alternative rows */
    -fx-control-inner-background-alt: derive(-fx-control-inner-background,-2%);
    ....
}

As you can see in the snippet above the -fx-base color is the base for the background color and the “inner” background color. All in all -fx-base can be found around 60 times inside the modena stylesheet.

In the stylesheets of our applications all we need to do now is to override -fx-base with a dark color like this:

.root {
    -fx-base: rgba(60, 63, 65, 255); // dark
}

The cool thing is that the text color is also dependent on this base color and Modena will pick a text color that has good contrast to the background color. As a result the application will end up using white for text. So we get this for free.

Obviously more fine-tuning will be needed to reach the final appearance but -fx-base will take us to our goal very quickly.

Happy coding everyone!

Invalid JavaFX Scroll Event Deltas?

BREAKING NEWS: this bug is now officially being tracked in the Java bug tracker.

I noticed today that the delta values provided by the JavaFX ScrollEvent object seem to be invalid at the end of the scroll event cycle. The absolute value of those delta values becomes bigger again even though they should end up in values around zero. I ran my tests on a Mac with the Magic Mouse and the Magic Touchpad. The values getting bigger causes stuttering in the UI, for example a swipe scroll inside the ListView will cause the ListView to finish the scrolling with a stutter at the end (see this video).

(Please notice that this is not relevant for mouse wheel scrolling as the mouse wheel will not continuously fire scroll events).

A typical output looks like this:

--- Scroll started ----
7.0001220703125 <-- first the values start to get bigger
10.0
13.000030517578125
12.0001220703125
12.0001220703125
13.000030517578125
12.0001220703125
13.000030517578125
12.0001220703125
11.00006103515625
10.0
9.000091552734375 <-- then the values are going down
9.000091552734375
8.000030517578125
8.000030517578125
7.0001220703125
7.0001220703125
6.00006103515625
6.00006103515625
6.00006103515625 <-- getting closer to zero (all good, normal so far)
14.000091552734375 <-- but now all of a sudden bigger values again
14.000091552734375 <--
13.000030517578125 <--
11.00006103515625 <--

The code below can be used to reproduce this issue. Simply start a scroll gesture with your touchpad or magic mouse on the label inside the window.

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class ScrollBugApp extends Application {

  @Override
  public void start(Stage primaryStage) throws Exception {
      Label label = new Label("Scroll On Me!");
      label.setAlignment(Pos.CENTER);
      label.setOnScrollStarted(evt -> System.out.println("--- Scroll started ----"));
      label.setOnScroll(evt -> System.out.println(evt.getDeltaY()));
      Scene scene = new Scene(label);
      primaryStage.setScene(scene);
      primaryStage.setWidth(200);
      primaryStage.setHeight(200);
      primaryStage.centerOnScreen();
      primaryStage.show();
 }

 public static void main(String[fusion_builder_container hundred_percent="yes" overflow="visible"][fusion_builder_row][fusion_builder_column type="1_1" background_position="left top" background_color="" border_size="" border_color="" border_style="solid" spacing="yes" background_image="" background_repeat="no-repeat" padding="" margin_top="0px" margin_bottom="0px" class="" id="" animation_type="" animation_speed="0.3" animation_direction="left" hide_on_mobile="no" center_content="no" min_height="none"][] args) {
      launch(args);
 }
}

If anyone knows of a work-around or a way to filter out those freak values at the end then please let me know.[/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

JavaFX Real-World Apps: Monastery Disentis

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”][fusion_text]

Finally the first “JavaFX Real World Apps” post that actually covers an “app” and not an “application”, meaning the first JavaFX application in this series that was designed for mobile devices and not the desktop. The application is simply called “Monastery Disentis”. It was developed by cnlab in Switzerland. It can be used by visitors of the monastery as a guide. Below you can see a couple of screenshots that were taken on an Android device.

Video: a screencast of the application.[/fusion_text][fusion_text]What is cool about this application is that everybody can try it out be following the links to the app stores, either the Apple App Store for iPhone users or Google Play for Android.

As usual I asked the development team a couple of questions and Daniel Zimmermann was nice enough to answer them below:

General Questions

What is the name of your product / project?

Monastery Disentis

Who are your users / customers?

Monastery of Disentis, Mustér, Switzerland.

What is the purpose of your software? What are its benefits?

Mobile app to guide the user through the monastery church.

Is the application operational? If yes, since when. If not when do you plan to go live?

Yes. It can be installed via the iTunes app store or Google play.

iTunes: https://itunes.apple.com/ch/app/hora-benedicti/id930800749?l=en&mt=8

Google Play: https://play.google.com/store/apps/details?id=ch.cnlab.horabenedicti

Development

How did you get the necessary JavaFX Know-How into your team? (Consultants, Internal / External training courses)?

Internal training.

With which version of JavaFX did you start? 1, 2, 8?

We started with version 8.

When did you start developing the application and how long did it take?

September, 2016, 6 Month with some pauses in between.

How many developers worked on it? In total and on the UI.

2 Developers in total. 1 exclusively on the UI.

How big is the application? Lines of code, Number of classes.

Lines of Code: 11000, No. of Classes: 113.

How big is the JavaFX client? Lines of code, Number of classes.

Lines of Code: 7500, No. of Classes: 51

Why did you choose JavaFX as frontend technology? And very importantly: why did you not choose HTML / Web?

The developer already knew Cordova, but wanted something else. The app‘s scope
seemed small enough to be used for it.

Was it difficult to convince decision makers to agree on JavaFX?

No. Since so we did not need to create two native apps with two developers.

What were the biggest challenges / problems / issues / bugs you faced in the JavaFX part and how did you solve them?

Rendering performance of Text nodes – heavily relied on asynchronous content loading and
lazy content display (in other words: lazily put it onto the Scene Graph).

Which 3rd-party products / frameworks / tools (open source and commercial) did you use and why did you choose them?

JavaFXPorts, Gluon Charm Down, ControlsFX, FontawesomeFX, jackson, commons-codec, Afterburner.fx, ScenicView.

Did you mix JavaFX and Swing code?

No.

Outlook

Would you use JavaFX again for your next project? Please elaborate why or why not.

Yes, but I will think twice for mobile, because the performance is not yet where it needs to
be on all platforms / phone variants.

Which recommendations do you have related to JavaFX for other companies / projects?

Not much, except for: Just give it a try. If you need platform independent Desktop
applications and/or don‘t want to rely on long-term browser support, this is a good and
good-looking alternative.

Which features would you like to see being added to JavaFX?

CSS closer to Web, faster CSS, improved layouting/API, faster text rendering performance.

[/fusion_text][/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

Interview on jaxenter.de

I was interviewed by Hartmut Schlosser of jaxenter.de last week and this week the article was published (in German) on their website. You can find it here: https://jaxenter.de/javafx-java9-javascript-56083. The interview was embedded in the “JavaFX Sixpack” series that tries to evaluate the potential of JavaFX. Hartmut was kind enough to also include information on my two commercial JavaFX frameworks CalendarFX and FlexGanttFX.

JavaOne Rockstar Award

I had already been informed a few weeks ago that I won one of the JavaOne Rockstar awards but today the award finally arrived. After planting a tree, building a house, marrying, having kids, and now this award there are probably not many achievements anymore waiting for me to unlock them. No wait …. mission accepted: Java Champion! 🙂

JavaFX Animation Tool

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”][fusion_text]Ok, I guess it is time to let you in on a little secret. The last three months or so I worked on a private project with the goal to create a tool that would allow me to easily create animations for Java desktop applications. JavaFX contains fantastic support on the API level for doing animations but for beginners or even intermediate level programmers it is not trivial to leverage it. However, when I use Apple’s Keynote for creating presentation slides, or when I see the animation / slider plugins for WordPress I realize how easy it can be to do animations, so why not bring the ease of these tools to JavaFX. What you can see in the screenshot below is the result of my work so far.

This is still at an early stage and things are subject to change but the basic idea is this: the output of the tool will be a presentation consisting of several slides. Each slide contains one or more elements. Element types are: region, node (code or fxml), images, and videos (more most likely to come). Each element can have any number of transitions associated with it. The background of the slides can be an image or a video or both. The image below shows a presentation with a background video, and three videos on top of it. Video playback starts when the user presses the green “play” button. However, the start of each video can be delayed by moving the “play” transitions further to the right / to the future, hence adding an initial delay.

It took me 60 seconds to create this second example, just to give you an idea on how much time you can save by using a tool.

Ok, that’s all for now. I hope I will find time to continue work on this tool and maybe present it at JavaOne this year. That is if Trump has resigned before then 🙂

Happy coding everyone!

 

P.S.: below you will find two YouTube videos showing the tool in action

[/fusion_text][fusion_text]Video 1, Video 2[/fusion_text][/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

JavaFX Tip 25: Use FXSampler!

The Problem

As a framework developer it is essential to have an easy way to individually test the appearance of and the interaction with each custom control. I really hate it when I first have to open five different screens before I finally get to my new control so that I can test it. Not only is it annoying but it also costs too much time. When doing UI work you constantly make changes to your controls and look at the impact of those changes. If a single change roundtrip (code, launch, test) takes 5 minutes then you can only squeeze 12 change iterations into an hour, making slow progress. If it takes 1 minute then you are looking at 60 iterations and fast progress. As simple as that.

The Solution

Luckily the ControlsFX project contains a very nice subproject called FXSampler, which is a generic sampler application for any JavaFX framework. I am currently using it for FlexGanttFX and CalendarFX. The screens below show the individual CalendarFX samples in a hierarchical tree structure on the left-hand side. The selected sample shows up in the center pane. In this case it is showing the view used for visualizing a calendar entry on a weekday. The property sheet on the right-hand side allows the user to edit the properties of the selected control. It should be noted that the right-hand side can show any kind of control for interacting with the sample, it doesn’t have to be a property sheet. However, very often the property sheet is the fastest way of coming up with a good way of manipulating the control.

The center pane has several tabs. The first one shows the actual sample. The second one displays the javadocs / API of the control. Ideally each sample shows exactly the API of the featured control and not just the index page of the entire framework API. Which page gets loaded into this tab can be configured individually for each sample.

The next tab is used for showing source code. This can be any code that you find relevant for the sample but in my case I always show the code of the sample itself.

Tab number four contains CSS styling information that is relevant for the selected sample. If your control overrides the getUserAgentStylesheet() method then the tab should display the stylesheet that is returned by this method. In CalendarFX all controls inherit from CalendarFXControl which does override this method and always returns “calendar.css”. So in this case there is only a single large CSS file and that is what is being displayed here.

The Setup

For my frameworks and projects I usually work with a multi-module Maven setup and add a separate module just for the samples. The pom.xml file of this module has to contain a dependency to FXSampler like this:

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

<dependency>
   <groupId>org.controlsfx</groupId>
   <artifactId>fxsampler</artifactId>
   <version>1.0.9</version>
</dependency>

The next thing needed is a class that represents the sampler project. The class has to implement the fxsampler.FXSamplerProject interface. It is used to specify a project name, a base package, and a welcome page. The code below shows the implementation of this interface as it was done for CalendarFX.

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

package com.calendarfx.demo;

import fxsampler.FXSamplerProject;
import fxsampler.model.WelcomePage;

public class CalendarFXSamplerProject implements FXSamplerProject {

   @Override
   public String getProjectName() {
      return "CalendarFX";
   }

   @Override
   public String getSampleBasePackage() {
      return "com.calendarfx.demo";
   }

   @Override
   public WelcomePage getWelcomePage() {
      return new CalendarFXSamplerWelcome();
   }
}

Welcome Page

The welcome page is optional and allows you to specify a title for the sampler and to show a UI that will be presented to the user when the sampler application starts up. The CalendarFX sampler shows a simple “about message”.

Base Package

FXSampler will use the “base package” to find all classes that are samples. FXSampler will place the samples found in the base package at the top of the navigation tree on the left-hand side of its window. Samples found in sub packages will end up in a tree node. The image below shows the package structure of the CalendarFX sampler project.

The base package for CalendarFX is com.calendarfx.demo and because the “day entry view” sample was found inside the entries package it means that the sample will be displayed in a tree node called “Entries”. The next image shows how the navigation tree reflects the package structure.

You will notice that the samples are all prefixed with “Hello…”. This is simply a convention I inherited from the ControlsFX project. There is no technical reason for it.

The Samples

A class becomes a sample if it implements the fxsampler.Sample interface. But it is even better if the class extends from fxsampler.SampleBase. The clever thing about this class is that it extends from Application, which means that each sample can also be run standalone.

A sample defines / contains the following things:

  • The name of the sample (in our example “Day Entry View”).
  • A short description of the sample / the control (what does it do? what is it used for?).
  • The name of the project that is belongs to.
  • The project version (e.g. 8.4.0).
  • A node that will be placed in the center pane and that displays the custom control.
  • A control panel for manipulating / interacting with the sample (right-hand side, often a property sheet).
  • The position of the control panel divider handle (based on the width requirements of the control panel).
  • The URL to the JavaDocs (I always point to the docs on my server).
  • The URL to the stylesheet that is relevant for the sample.
  • The URL to the source code of the sample.
  • A flag to indicate if the sample is currently visible (good for hiding samples that are still work-in-progress).

The Launcher

To run the sampler project we need a class with a main() method that extends the JavaFX Application class. Luckily FXSampler ships a class called fxsampler.FXSampler. For CalendarFX it looks like this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

package com.calendarfx.demo;

import fxsampler.FXSampler;
import javafx.stage.Stage;

public class CalendarFXSampler extends FXSampler {

   @Override
   public void start(Stage primaryStage) throws Exception {
      super.start(primaryStage);
   }

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

You will notice that the launcher does not reference / instantiate any specific sampler project. You would normally expect to see the  launcher create an instance of CalendarFXSamplerProject but its’ main() method simply delegates to the main() method of FXSampler. So how does it find anything?

The Service Provider

The answer is simple but not something that every Java developer has encountered in their day to day coding routine. FXSampler finds the sampler project because it will be registered as a “service provider”. This is done by adding a file to the directory META-INF/services. The file name has to be fxsampler.FXSamplerProject. Inside the file we add the full class name of the sampler project. FXSampler can now lookup all service providers that implement the FXSamplerProject and instantiate them. This allows FXSampler to show the samples found in several JARs at the same time. If I wanted to I could create a single application showcasing the samples found in FlexGanttFX, CalendarFX, and ControlsFX at the same time. The image below shows the location of the service provider file and its content inside the CalendarFXSampler module.

The Executable

When you download CalendarFX or FlexGanttFX you will see that the distributions include the sampler projects (inside “demos” folder). They were added as executable / runnable JAR files. The required Maven configuration for building them looks like this:

[/fusion_builder_column][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]

<build>
   <plugins>
      <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <version>2.4</version>
         <configuration>
            <finalName>sampler-demo</finalName>
            <appendAssemblyId>false</appendAssemblyId>
            <descriptorRefs>
               <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
            <archive>
               <manifest>
                  <mainClass>com.calendarfx.demo.CalendarFXSampler</mainClass>
               </manifest>
            </archive>
         </configuration>
         <executions>
            <execution>
               <id>make-samples</id>
               <phase>package</phase>
               <goals>
                  <goal>single</goal>
               </goals>
            </execution>
         </executions>
      </plugin>
   </plugins>
</build>

Conclusion

I highly recommend using FXSampler and making it a habbit to add a sample for every custom control you write, no matter if you are a working on a framework or an application. The benefits of being able to quickly test a control in a controlled environment greatly outweighs the overhead of creating and maintaining the sample.

Happy coding everyone![/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]