Hello, DEMUX for JavaFX

From DEMUX Framework Wiki
(Redirected from Hello, DEMUX)
Jump to: navigation, search

This tutorial will help you get started with DEMUX Framework. It provides overview of basic concepts and features of the framework. We will use few simple example projects which are available as part of project distribution.

In this tutorial, we will build simple JavaFX desktop application. Source code can be found in binary distribution package, under samples/hello-demux-jfx directory. Furthermore, we will design this application so it can be easily ported to Android in the next tutorial.

Contents

Creating the project

We will start with creating a project template for JavaFX desktop application. DEMUX Framework provides a set of Maven archetypes which generate predefined project structure and allow you to focus on building the actual appliaction. These archetypes are available from Maven Central repository. Currently, the following archetypes are available:

  • DEMUX JavaFX archetype, which enables development of JavaFX applications
  • DEMUX Android archetype, which enables development of Android applications
  • DEMUX Bundle archetype, which enables development of OSGI bundles which forma applications

For this tutorial, we will use demux-jfx-archetype. Maven Central repository holds hundreds of archetypes. To get the one we need, run the following command:

mvn archetype:generate -Dfilter=com.vektorsoft.demux.tools:demux-jfx-archetype

You should get the following output.

Choose archetype:
1: remote -> com.vektorsoft.demux.tools:demux-jfx-archetype (-)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): :

Important: Archetypes are maintained in Maven Central repository, which contains archetype catalog file of all archetypes in repository. This file is updated every Sunday, so it is possible that latest version of framework archetypes are contained in file. This is especially true in this stage of development, where new version are released frequently.

In case the latest version is not present, you can use the following command line to get the latest version:

mvn archetype:generate -DarchetypeGroupId=com.vektorsoft.demux.tools -DarchetypeArtifactId=demux-jfx-archetype -DarchetypeVersion=0.7.3 -DarchetypeRepository=central

Choose demux-jfx-archetype (1) archetype and enter required information (groupId, artifactId, version and package), as shown bellow:

Choose a number: : 1
Define value for property 'groupId': : com.vektorsoft.demux.samples
Define value for property 'artifactId': : hello-demux
Define value for property 'version':  1.0-SNAPSHOT: : 1.0.0
Define value for property 'package':  com.vektorsoft.demux.samples: : 
Define value for property 'demuxFrameworkVersion': : 0.8.3

After you execute these commands, you will get completely setup project for building JavaFX application. Since it is Maven project, you can open it with any mainstream IDE (Eclipse, NetBeans, IDEA). You can also complete the tasks bellow using your IDE. For more information abou this, pleasee see the article on using your favourite IDE.

At this point, you got completely functional JavaFX application. To build and run it, execute the following Maven commands in project directory:

mvn install exec:exec

You should get the window like the one displayed bellow.

Running JavaFX application on DEMUX Framework

As you have probably noticed, a splash screen appears displaying loading progress. This progress bar shows loading progress of application bundles. You can customize this splash screen and other parts of your application in the section about customizing applications.

Important note: On Mac OS X, with some older versions of JavaFX, there is a bug that prevents JavaFX from launching when AWT splash screen is used. In this case, you will only get splash screen, and application will be blocked. To bypass this issue, change Maven Exec plugin configuration to the following:

<arguments>
   <argument>-cp</argument>
   <argument>demux-desktop-jfx.jar:osgi.jar</argument>
   <argument>com.javafx.main.Main</argument>
</arguments>

Maven will create application in directory hello-demux-1.0.0 under target/dist.

Create application bundle

DEMUX Framework is based on OSGI, so the basic application building block is OSGI bundle. There is a Maven archetype provided to help you get started with building bundles. These bundles contain views and controllers that make up the application. To create bundle project, change to previously created project directory, and create project from archetype as before, but this time select demux-bundle-archetype. This procedure is shown bellow:

mvn archetype:generate -Dfilter=com.vektorsoft.demux.tools:demux-bundle-archetype
....
Choose archetype:
1: remote -> com.vektorsoft.demux.tools:demux-bundle-archetype (-)
Choose a number: : 1
Define value for property 'groupId': : com.vektorsoft.demux.samples
Define value for property 'artifactId': : hello-demux-common-module 
Define value for property 'version':  1.0-SNAPSHOT: : 1.0.0
Define value for property 'package':  com.vektorsoft.demux.samples: : 
Define value for property 'bundleSymbolicName': : hello-bundle-symbolyc-name

This will create new Maven project as a child module of the first project. You can build each bundle independently of the others, and they will be copied to the correct application directory. This way, you can avoid rebuilding entire application, and only rebuild the bundles that are changed. To try this, change to newly created directory and run mvn install. You should see new bundle appearing under hello-demux-1.0.0/bundles directory in top level target/dist.

Run the top-level project again, but this time use the following command line:

mvn exec:exec -pl .


This will prevent Maven exec:exec goal to run in submodule, but rather in top level project only. Although bundles are configured to skip this step, it can still take time for Maven to iterate over all submodules. By specifying -pl ., you make sure that goal is executed only for top-level project.

Create application GUI

To emphasize possibility of code reuse, we will separate application code into two separate modules. One module will contain application logic, and the other will contain GUI code, which is platform specific. In this tutorial, we will use JavaFX as GUI toolkit, and in the next we will port application to Android and use Android GUI toolkit. To create module for GUI, we will run project creation wizard again, as in previous step:

mvn archetype:generate -Dfilter=com.vektorsoft.demux.tools:demux-bundle-archetype
....
Choose archetype:
1: remote -> com.vektorsoft.demux.tools:demux-bundle-archetype (-)
Choose a number: : 1
Define value for property 'groupId': : com.vektorsoft.demux.samples
Define value for property 'artifactId': : hello-demux-jfx-module 
Define value for property 'version':  1.0-SNAPSHOT: : 1.0.0
Define value for property 'package':  com.vektorsoft.demux.samples: : 
Define value for property 'bundleSymbolicName': : hello-demux-jfx-module

Now it's time to create some GUI for our application. DEMUX Framework enforces clear separation of concerns using adapted MVC pattern called Model-View-Adapter. In this sense, GUI components are assembled into views, which are used to display information and collect user input. All views should implement DMXView interface. To make things easier for implementation, framework provides abstract class called DMXAbstractView which can be used as a base class for views. We will use this approach to make our sample view. This is shown in a code snippet bellow:


public class HelloView extends DMXAbstractView {
 
    // UI stuff
    private HBox hbox;
    private Text text;
    private Button button;
 
    // model data
    private boolean testData;
 
    public HelloView(DMXAdapter adapter){
        super(adapter);
    }
 
    @Override
    public void render() {
        if(testData){
            text.setFill(Color.GREEN);
        } else {
            text.setFill(Color.RED);
        }
    }
 
    @Override
    public void constructUI() {
        hbox = new HBox();
        text = new Text("Hello, DEMUX!");
        button = new Button("Click me");
        hbox.getChildren().add(text);
        hbox.getChildren().add(button);
        hbox.setPadding(new Insets(5, 10, 15, 20));
 
    }
 
    @Override
    public String getParentViewId() {
        return JFXViewManager.JFX_ROOT_VIEW_ID;
    }
 
    @Override
    public void updateFromModel(Map<String, Object> dataMap) {
        testData = (Boolean)(dataMap.get("testData"));
    }
 
    @Override
    public Object getViewUI() {
        return hbox;
    }
 
    @Override
    protected void loadDataIds() {
       dataIds.add("testData");
    }

Place this class in a package under hello-demux-jfx-module project. Fileds hbox, text, button are JavaFX UI controls, and field testData is model data. Purpose of each method is explained bellow:

  • render() method draws the view based on model data. It is called each time model data is changed to refresh view.
  • constructUI() method is used for initial creation of user interface. This method is always called from UI thread of the underlying platform to enable correct Ui construction
  • getParentViewId() returns name of parent view of this view. Views are arranged in hierarchy where each view can have multiple views as children
  • updateFromModel() will update view data from model. Current model data is passed in as map, where data ID is map key and data value is map value. In this case, we set value of testData to one supplied from view.
  • getViewUI() returns object that represents UI component of this view.
  • loadDataIds() adds IDs of model into set of data IDs defined for this view. In this case, we add ID testData to set of

Now that the view is defined, we need to register it with MVA Adapter. DEMUX Framework provides DesktopAdapter class which is registered as OSGI service, and can be access by bundles. Bundle archetype generates OSGI BundleActivator implementation (SampleActivator and ServiceTrackerCustomizer implementation SampleCustomizer. Locate SampleCustomizer class and add the following code to addingService() method:

@Override
    public DMXAdapter addingService(ServiceReference<DMXAdapter> sr) {
        DMXAdapter svc = (DMXAdapter)context.getService(sr);
 
        // register views and controllers here
        HelloView view = new HelloView();
        svc.registerView(view);
       return svc;
    }

At this point, you can build and run the project using the following commands (in top-level directory)

mvn clean install
mvn exec:exec -pl .

You should see a window like the one shown bellow.

DEMUX Window with sample view

As you can see, string "Hello, DEMUX!" is rendered in red, since the default value of testData is false.

Create application logic

At this point, when you click the button, nothing happens. In this section, we will add a controller to our application. Controllers operate on model data and process user input. As with view, we will need to register controller with the Adapter. The code bellow should be placed under hello-demux-common-module project.

Controllers implement DMXController interface which defines all method that controller must implement. As with views, there is an abstract class DMXAbstractController which facilitates this. We will create new HelloController as shown bellow:

public class HelloController extends DMXAbstractController {
    // model data we are interested in
    private boolean testData;
 
    @Override
    protected void updateDataMapFromFields() {
        dataMap.put("testData", testData);
    }
 
    @Override
    public void updateFromModel(Map<String, Object> modelData) {
        testData = (Boolean)dataMap.get("testData");
    }
 
    @Override
    public void execute(Object... params) throws ExecutionException {
        testData = !testData;
    }
 
}

Here, field testData is the model data we will change. It needs to be registered under the same name as in the view, so it can get synchronized correctly. Note that we don't mean actual variable name, but rather the string under which it is registered with model. We implement the following methods:

  • updateDataMapfromFields() - there is protected field dataMap in DMXAbstractController which holds data this controller registers with the model, and their names. This method will populate that map with fields from implementation. In this case, we want to register data with ID "testData" with value of variable testData. Note that string "testData" must be the same in both controller and view, but the actual field can be named anything.
  • updateFromModel() - this method will update class fields with data from model, which is passed in as map. In this case, we set field testData to the value of model data with ID "testData.
  • execute() - this is where the actual controller logic goes. In this case, we just want to flip value of testData. This change is automatically reflected in model, causing the view to redraw itself.

Finally, we need to register controller with the adapter. Just like with the view, we do it in SampleCustomizer.

public DMXAdapter addingService(ServiceReference<DMXAdapter> sr) {
        DMXAdapter svc = (DMXAdapter)context.getService(sr);
 
        // register views and controllers here
        svc.registerController(new HelloController());
       return svc;
    }
At this point, we have both view and controller set up, but still nothing happens when you click on the button. We still need to add event handler to the button which will handle events. We will do that in the next section.

Decoupling logic from view

In traditional GUI applications, the common way to handle events is to write GUI code, and register event listeners on GUI components for the events we want to process. This approach, although simple and straight forward, can lead to large and confusing code, since event listeners are usually anonymous inner classes, which can grow to large proportions. Furthermore, this approach add tight coupling between GUI and application logic.

With DEMUX Framework, we can avoid this issue completely. As we can see from our code so far, our view does not contain any logic at all, and our controller is completely ignorant of any view existing. DEMUX Framework provides support to register event handlers for GUI components out of the view code, and these event handlers invoke controllers which handle business logic. The way to achieve this is by using com.vektorsoft.demux.core.mva.DMXEventRegistrationHandler<T> interface. This interface defines the following methods:

public interface DMXEventRegistrationHandler<T> {
 
 
    String[] getComponentIds();
 
 
    T getEventHandler();
}

As we can see, this interface is quite simple, it contains only two methods.

  • getComponentIds() - returns array of IDs of components on which event handler will be registered. This means that each component for which we want to register event handler must have unique ID within the application.
  • getEventHandler() - returns actual event handler which will process events

Since GUI system, events and event handlers are extremely platform-specific, each supported platform contains custom implementations of this interface. Using it in JavaFX is shown in the next section.

Event registration in Java FX

The base class for registering event in Java FX is com.vektorsoft.demux.desktop.gui.JFXEventHandler<T>. This is convenience class intended to be extended for custom event handlers. The code samples bellow shows action listener extended from this class:

public class ClickHandler extends JFXEventHandler<ActionEvent> {
 
    public ClickHandler(DMXAdapter adapter, EventType type){
        super(adapter, type);
        setComponentIds("clickButton");
    }
 
    @Override
    public void handle(ActionEvent t) {
        adapter.invokeController("com.vektorsoft.demux.samples.hello.HelloController");
    }
 
 
}

This simple class contains only one method. Method handle() is simply inherited from basic Java FX EventHandler interface. This method simply invokes the controller we have registered earlier. One more thing to note is that, in constructor, we call method setComponentIds(), whose arguments are IDs of the components on which we want to register the event handler. In order to make this work correctly, we need to set button ID on our view class:

.................
text = new Text("Hello, DEMUX!");
button = new Button("Click me");
button.setId("clickButton");
.....................

Finally, all that is left is to register event handler:

ublic DMXAdapter addingService(ServiceReference<DMXAdapter> sr) {
...........
svc.registerEventHandler(new ClickHandler(svc, ActionEvent.ACTION));
.........
}


Now you can build and run the application, and see how text changes color on button click.

Dmx-window-view.png

Dmx-window-view-ctrl.png

At this point, you have fully functional JavaFX application. To see how you can reuse created modules, and deploy applications to different platforms, pleas see the next tutorial, where we will port this application to Android. Or, you can see how you can prepare application for deployment on supported platforms through native installation packages.

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox