Desktop Java

Building Rich Clients with JacpFX and JavaFX2

Creating fast and scaling desktop clients is always a challenge, particularly when dealing with a large amount of data and long running tasks. Although Eclipse RCP and Netbeans RCP are established platforms, the idea was to build a lightweight framework that handles components asynchronously, similar to web components. Developers should have less effort on threading topics and should be able to model the applications’ message flow on their own. These, and many other ideas resulted in the JacpFX project.

JacpFX

The JacpFX (Java Asynchronous Client Platform) Project is a Framework to create Rich Clients (Desktop and/or Web) in MVC style with JavaFX 2, Spring and an Actor like component approach. It provides a simple API to create a workspace, perspectives, and components; to communicate with all parts and to compose your Client application easily.

What does JacpFX offer to you?

  • Implement scaling and responsive Rich Clients against a small and simple API (~120Kb) in Java
    • Fully integrated with Spring and JavaFX 2
  • Add/move/remove defined Components at run-time in your UI
    • Create your basic layout in Perspectives and define “placeholders” for your Component UI
  • Handle Components outside the FX application thread
    • “handle” methods are executed in a worker thread and a “posthandle” method is executed by the FX application thread
  • Stateful and stateless callback non-UI components
    • Outsource long running processes and computation
  • Handle asynchronous processes easily
    • No need for explicit threading or things like Runtime.invoke()
  • Communicate through Asynchronous Messages
  • No shared data between Components
  • Immutable Messages

The following article will give you a short introduction on JacpFX and how to create JavaFX 2 based Rich Clients with it. The example client shown here, is a (pseudo) contact manager that you can try out here: http://developer.ahcp.de/demo/JACPFX2Demo.html ; The complete source code can you download here: http://code.google.com/p/jacp/downloads/list

Pre-requirements :

A valid JavaFX2 run-time must be installed to run the demo application (currently available on windows only). To compile the attached demo code, a JavaFX2 SDK (also available for Mac and Linux) and Apache Maven is assumed to be installed. See the detailed instructions at the end of this article:

Image 1: Select a category for the first time and the client will ask you to generate 250.000 contacts. While those contacts are generated and incrementally added to the table view, you can select the next category, browse to the next table page, view the consumer chart data or just edit a contact. Keep in mind that no explicit threading was used in the demo-client code

Application – Structure

A JacpFX application is composed of an application-launcher, a workbench, minimum one perspective and at least one component. The example client uses three UI-Components and three non-UI (Callback) Components to create the data and to simulate heavy data access.

The Application – Launcher (AFX2SpringLauncher)

The application – launcher is the main class of your application where you define the Spring context. A JacpFX – application uses xml declaration to define the hierarchy of the application and all the meta-data, like “component-id” and “execution-target”. The Spring main.xml is located in the resources directory and will be declared in the launchers constructor:

public class ContactMain extends AFX2SpringLauncher {
    public ContactMain() {
        super("main.xml");
    }
    public static void main(String[] args) {
        Application.launch(args);
    }
    @Override
    public void postInit(Stage stage) {
        // define your css and other config stuff here
    }
}

Listing 1: application launcher

The Application – Workbench (AFX2Workbench)

The workbench is the UI-root node of a JacpFX application. Here you configure the application, set the resolution, define tool – bars, menus and have reference to the JavaFX stage.

public class ContactWorkbench extends AFX2Workbench {
    @Override
    public void handleInitialLayout(IAction<Event, Object> action,
            IWorkbenchLayout<Node> layout, Stage stage) {
        layout.setWorkbenchXYSize(1024, 768);
        layout.registerToolBar(ToolbarPosition.NORTH);
        layout.setMenuEnabled(true);
    }

    @Override
    public void postHandle(FX2ComponentLayout layout) {
        final MenuBar menu = layout.getMenu();
        final Menu menuFile = new Menu("File");
        final MenuItem itemHelp = new MenuItem("Help");
        /// add the event listener and show an option-pane with some help text
        menuFile.getItems().add(itemHelp);
        menu.getMenus().addAll(menuFile);    }
}

Listing 2: Workbench

The Perspective (AFX2Perspective)

The task of a perspective is to provide the layout of the current view and to register the root and all leaf nodes of the view. The leaf nodes are the “execution-target” (container) for all components associated (injected) to this perspective. The demo perspective basically defines two SplitPanes and registers them as “execution-target” to display the content of the ContactTreeView on the left; the ContactTableView at top right and the ContactChartView at bottom right.

Image 2: Three targets defined by the perspective in the demo
public class ContactPerspective extends AFX2Perspective {
    @Override
    public void onStartPerspective(FX2ComponentLayout layout) {
    // create button in toolbar; button should switch top and bottom id's 
        ToolBar north = layout
                .getRegisteredToolBar(ToolbarPosition.NORTH);
...
        north.getItems().add(new Button("switch view");) ;
    }

    @Override
    public void onTearDownPerspective(FX2ComponentLayout layout) {   }

    @Override
    public void handlePerspective(IAction<Event, Object> action,
            FX2PerspectiveLayout perspectiveLayout) {
        if (action.getLastMessage().equals(MessageUtil.INIT)) {
            createPerspectiveLayout(perspectiveLayout);
        }     
}
    private void createPerspectiveLayout(FX2PerspectiveLayout perspectiveLayout) {
        //// define your UI layout
        ...
        // Register root component
        perspectiveLayout.registerRootComponent(mainLayout);
        // register left menu
        perspectiveLayout.registerTargetLayoutComponent("PleftMenu", leftMenu);
        // register main content Top
        perspectiveLayout.registerTargetLayoutComponent(“PmainContentTop”,
                        mainContentTop);
        // register main content Bottom
        perspectiveLayout.registerTargetLayoutComponent("PmainContentBottom",
                mainContentBottom);
    }

}

Listing 3: Perspective

The UI-Components (AFX2Component)

AFX2Components are the actual UI-Components in a JacpFX application that are rendered as JavaFX components. The demo defines a left (ContactTreeView), main-top (ContactTableView) and a main-bottom (ContactDemoChartView) AFX2Component. A JacpFX component has four methods to implement; “onStartComponent” and “onTearDownComponent” as well as “handleAction” and “postHandleAction”. While the “handleAction” is executed in a worker thread the “postHandle” runs in the FX application thread.

Image 3: Component lifesycle of an AFX2Component

You can use lazy initialization of components and shutdown or restart components if you want.

public class ContactTreeView extends AFX2Component {
    private ObservableList<Contact> contactList;
    ...
    @Override
    public Node handleAction(final IAction<Event, Object> action) {
        if (action.getLastMessage().equals(MessageUtil.INIT)) {
           this.pane = new ScrollPane();        ...
         return this.pane;
        }
        return null;
    }

    @Override
    public Node postHandleAction(final Node node,
            final IAction<Event, Object> action) {
        if (action.getLastMessage() instanceof Contact) {
            this.contactList.addAll((Contact) action.getLastMessage());
        }
        return this.pane;
    }

    @Override
    public void onStartComponent(final FX2ComponentLayout layout) {
        final ToolBar north = layout
                .getRegisteredToolBar(ToolbarPosition.NORTH);
       …
        north.add(new Button("add category"));
    }
   
    @Override
    public void onTearDownComponent(final FX2ComponentLayout layout) {    }
    ...    
}

Listing 4: ContactTreeView (the left view)

The “handleAction” method in Listing 4 is used to initialize the component UI. In the “postHandle” action of the demo new contacts were added; the contactList is bound to the existing TreeView, so you can’t update it outside the FX application thread and must therefore use the “postHandle” method.

The Callback Components

JacpFX callback-components are non-UI/service components. Similar to an AFX2Component, they have a method called “handle” that is executed in a worker thread. The result can be any type of object and will be automatically delivered back to the calling component or redirected to any other component.

In these types of components you execute long running tasks, invoke service calls or simply retrieve data from the storage. JacpFX provides two types of callback components, an “AStatelessCallbackComponent” and a (stateful) “ACallbackComponent”. The demo client uses an “ACallbackComponent” component to generate the random chart data for a selected contact a table.

To generate the large amount of contacts two “AStatelessCallbackComponents” are involved. One component divides the total amount into chunks and the second one just creates contacts. The result will be sent to the UI component directly and added to the table.

Messaging

Messaging is the essences of the JacpFX Framework. JacpFX uses asynchronous messaging to notify components and perspectives of your application.

The message flow to create the initial amount of data for a category in the demo application looks like:

Image 4 : Message Flow to create initial amount of data in the demo application

The state of a JacpFX component is always changed by messages. If a task should be handled in a background thread you simply send a message to a component and process the work in the “handle” method. The result can be sent back to the caller component or be processed by the FX application thread in the “postHandle” method (in case of UI Components). You should always avoid execution of long running tasks on the FX application thread; instead call your service- or DB-operation inside the “handle” method.

JacpFX differs between two message types, a local and a global message.

Local messages

To trigger a local message simply get a listener

IActionListener<EventHandler<Event>, Event, Object> listener =   this.getActionListener(“message”);

with only one argument – the message itself. This listener can be assigned to any JavaFX eventHandler (like onMouseEvent, etc.) or it can be fired by invoking

listener.performAction(event);

Global messages

With global messages you communicate with other registered components. Callback components respond to a message by default so you don’t need to create a respond message explicitly – also you could. Messaging is the prefered way to exchange data between components and to trigger Tasks. Similar to local messages you create a listener instance but with an explicit target id:

IActionListener<EventHandler<Event>, Event, Object> listener =   this.getActionListener(“id“,“message”);

Build the demo application from source

Register JavaFX in your local Maven repository

All JacpFX-Projects are Maven projects and require JavaFX 2 (jfxrt.jar). Some include JavaFX 2 as system dependency but we preferred to register the jfxrt.jar in the local repository. To create deployable files (jnlp and html) you additionally need to register the JavaFX Ant-Tasks (ant-javafx.jar). To do so change to your ${SDK-home}/rt/lib and type:

mvn install:install-file -Dfile=jfxrt.jar -DgroupId=com.oracle -DartifactId=javafx-runtime -Dpackaging=jar -Dversion=2.0

Then copy the “bin” directory (on linux i386) to your .m2\repository\com\oracle\javafx-runtime folder. Next change to your ${SDK-home}/tools directory and type:

mvn install:install-file -Dfile=ant-javafx.jar -DgroupId=com.oracle -DartifactId=ant-javafx -Dpackaging=jar -Dversion=2.0

Build the project

To build the project, simply unzip the project folder and type mvn package. The jar, jnlp and the html file is created in the ${projectHome}/target/deploy. To create an Eclipse project simply type mvn eclipse:eclipse.

What’s coming next?

JacpFX is currently in Version 1.0; after many years of prototyping with Echo3 and Swing, JacpFX is the first stable release based on JavaFX 2 and a defined release plan. You can find a detailed documentation on the projects Wiki page (http://code.google.com/p/jacp/wiki/Documentation). Our release plan (also located at the Wiki) defines Version 1.1 in June this year. Major changes will be annotation support and an official FXML support (also you can already use FXML). Feedback is always welcome, so feel free to contact us.

Reference: Building Rich Clients with JacpFX and JavaFX2 from our W4G partner Andy Moncsek.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button