Project

General

Profile

Building a view

The composition mechanism

The composition mechanism integrated in AF3's kernel aims at improving the reusability of both layouts and controllers and reduce the overall complexity of user interface development through modularity.

Complex user interfaces can be hierarchically decomposed into smaller, reusable views w.r.t. both layout and logic. This is achieved by allowing each view to define containers where sub-views can be inserted. This way repeating UI elements can be defined once, and reused wherever needed.

Each view refers to its own FXML layout file and implements the controller logic for the interface elements it defines. This way not only the layout can be reused, but - most notably - the view's logic as well.

The composition framework is build around the ICompositeFXController, an interface for the hierarchic composition of controllers (and their layouts, respectively). Controllers implementing this interface can be containments of other such controllers and take containments as well. The following figure illustrates the concept:

The illustration also captures how the hierarchic controllers are integrated with AutoFOCUS 3: As ICompositeFXController s can - and should be - reused in multiple views, they cannot be used to uniquely identify a single view. Thus, for AF3 to reference all views within plugin.xml files, an almost empty class deriving from AF3FXViewPart is created for each of them. It contains nothing but a constructor with no parameters, passing an instance of the top-most ICompositeFXController to the super constructor.

This presentation includes a more comprehensive explanation of the composition framework and how it is applied: af3_javafx_composites.pdf

Creating a user interface.

As the loading mechanism for FXML files is common to all JavaFX controllers, the abstract CompositeFXControllerBase class is introduced. It implements the ICompositeFXController interface and implements the getOrLoadLayout() method while keeping all other methods abstract.

With the loading logic taken care of, just follow these steps to create a new view within AF3:

  1. Create a new class for the view's controller extending CompositeFXControllerBase.
  2. If the view is supposed contain other IComponentFXController s, pass them to the super constructor (otherwise call it without any argument).
  3. Declare the name of the FXML file by overriding the getFXMLLocation().
  4. Specify how to initialize the view and how to add the given containments (if there are any) in the initialize(ICompositeFXController[] containments) method.
  5. In order to register a view within the Eclipse platform framework, the top-most component has to be referenced by a unique wrapper class. This is achieved by creating a class deriving from AF3FXViewpart, containing nothing but a constructor with no parameters, passing an instance of the top-most ICompositeFXController to the super constructor. This class can then be registered in a plugin.xml file as a view.

Appendix

Examples

If you don't want to follow along step-by-step, here are the FXML and Java files for the following examples:

Let's say hello to the world.

  1. Navigate to the playground package in the src folder of the org.fortiss.af3.exploration.ui plugin.*
  2. Create a new class, called HelloWorldFXController.java, deriving from CompositeFXControllerBase. The IDE will then either tell you to implement the parent's abstract methods or it will create stubs for the getFXMLLocation and initialize methods.
  3. As of now, we don't have anything to initialize, thus you can leave the respective method empty (but don't forget to comment the reason for the empty block!).
  4. Now let's take care of the view's layout. Start by using the getFXMLLocation method to return the name of the FXML file. In our case the only line in that method will read return "HelloWorldLayout.fxml". Your class should look something like this by now:
    package org.fortiss.af3.exploration.ui.playground;
    
    import org.fortiss.tooling.common.ui.javafx.layout.CompositeFXControllerBase;
    import org.fortiss.tooling.common.ui.javafx.layout.ICompositeFXController;
    
    /**
     * Let's say hello to the world.
     * 
     * Controller class for the "Hello world!" view. References the FXML layout and contains all control
     * logic.
     * 
     * @author munaro
     */
    public class HelloWorldFXController extends CompositeFXControllerBase {
    
        /** {@inheritDoc} */
        @Override
        public String getFXMLLocation() {
            return "HelloWorldLayout.fxml";
        }
    
        /** {@inheritDoc} */
        @Override
        public void initialize(ICompositeFXController[] containments) {
            // Nothing to do
        }
    }
    
  5. Now navigate to the same playground package within the same plugin - but this time in the res folder! Once there, create a new HelloWorldLayout.fxml file and open it using the SceneBuilder (see Appendix on how to set it up). Make sure that your file and folder structure looks like this by now:
  6. In the SceneBuilder drag and drop a Pane in the preview window and resize it as you want. Then do the same with a Label, placing it somewhere on the pane. If you don't like the label to read "Label" feel free to change the text. Once you're happy with the result save the file and switch back to Eclipse.
  7. The view is now complete! However, we still need to register it in the plugin's plugin.xml file, otherwise you won't be able to open it. To do so, navigate to the same package where the HelloWorldFXController.java is located (back in the src folder) and create another class called "HelloWorldFXViewPart.java", this time deriving from AF3FXViewPart. Now add a constructor (Eclipse will force you anyways), and remove all parameters. It is only needed to contain a call to the super constructor passing an instance of the HelloWorldFXController (as we don't have CSS style sheets, the second parameter can be null).
    package org.fortiss.af3.exploration.ui.playground;
    
    import org.fortiss.tooling.common.ui.javafx.AF3FXViewPart;
    
    /**
     * Let's say hello to the world.
     * 
     * Wrapper for the {@link HelloWorldFXViewPart}. This class can be referenced in the
     * {@code plugins.xml} file to register the view.
     * 
     * @author munaro
     */
    public class HelloWorldFXViewPart extends AF3FXViewPart {
    
        /** Constructor. */
        public HelloWorldFXViewPart() throws Exception {
            super(new HelloWorldFXController(), null);
        }
    }
    
  8. The last step: Register the view by adding a view extension to the plugin.xml file as follows:
    <view
        class="org.fortiss.af3.exploration.ui.playground.HelloWorldFXViewPart" 
        id="org.fortiss.af3.exploration.ui.playground.HelloWorldFXViewPart" 
        name="Hello World" 
        restorable="true">
    </view>
  9. Now run AF3, type "Hello World" into the "Quick Access" and enjoy your view!

* Note that the same GUI can be created in any other plugin and package as long as it can access the org.fortiss.tooling.common.ui kernel plugin. However, in the second part of this example we will use another view defined in the org.fortiss.af3.exploration.ui. Thus, it is recommended to create the view in the same plugin so as to keep things as straightforward as possible in this example.

Let's compose something.

You have decided that you want to add your "Hello World" to the DSE view, with the same frame containing the "Back" and the "Help" buttons. Here comes the magic:

  1. Open the HelloWorldFXViewPart.java and instead of passing an instance of the HelloWorldFXController to the super constructor, we now pass a new instance of the ExplorationUIFXController - to which we pass the new instance of the HelloWorldFXController as well as a title we want to show in the header bar e.g. "Hello World!". This way
    super(new HelloWorldFXController(), null);

    becomes
    super(new ExplorationUIFXController(new HelloWorldFXController(), "Hello World!"), null);
  2. That's all. Run AF3 and open your view!

Let's get some action going.

You like greetings but want something more? Your fancy some buttons? Luckily, we've got you covered:

  1. Open the HelloWorldLayout.fxml using the SceneBuilder:
    1. Add a TextField with the fx:id set to nameTextField. The respective setting is located in the Code tab of the right accordion.
    2. Add a Button with the fx:id set to submitButton. In the same Code tab where you set the fx:id look for the onAction option and set it to onSubmit.
    3. Delete the text of the "Hello World!" label and set its fx:id to displayLabel.
    4. Don't forget to save your changes!
  2. Now switch back to Eclipse to implement some actions:
    1. Open the HelloWorldFXController class.
    2. Reference each UI element by creating a private instance variable with the respecive JavaFX type for each of them. Make sure to annotate each of them with the FXML annotation and to name them EXACTLY the same as their fx:id!
      /** {@link TextField} for inserting names. */
      @FXML
      private TextField nameTextField;
      
      /** {@link Button} to submit the inserted name. */
      @FXML
      private Button submitButton;
      
      /** {@link Label} to display the greeting. */
      @FXML
      private Label displayLabel;
      
    3. Now we want to make sure that the label we want to display our greetings on is (almost) empty when the view is openend. Thus, let's initialize it with "...":
      /** {@inheritDoc} */
      @Override
      public void initialize(ICompositeFXController[] containments) {
          displayLabel.setText("...");
      }
      
    4. Finally, we want to update the label to show a greeting when the submit button is pressed. For that we need to create a new public method (make sure it's public!) named EXACTLY the same as the onAction property you set in the SceneBuilder. As the greeting should contain the user's name, you can now use nameTextField.getText() to extract the inserted name. To display the greeting, the displayLabel.setText(String value) is the way to go. In the end the method should look something like this:
      /** Displays the greeting when the "Submit" button is pressed. */
      public void onSubmit() {
          displayLabel.setText(format("Hello %s!", nameTextField.getText()));
      }
      
  3. You're done! Run AF3 and start typing and clicking on your new interactive view!

About JavaFX

JavaFX is a GUI framework and toolkit that allows building GUIs with less effort compared to SWT. GUIs can be created programmatically or using a layout files in combination with a controller class that is programmed in Java.

The integration of JavaFX in AutoFOCUS 3 (AF3) is achieved by using the e(fx)clipse plugins developed as an Eclipse project led by Thomas Schindl.

How to set up the SceneBuilder

When installing AF3, the platform integration is already shipped. However, you may want to install the SceneBuilder to work with FXML layout files to build complex GUIs.

  1. Install SceneBuilder to your machine.
  2. In your developer installation, navigate to "Window > Preferences" and click "JavaFX". In the box to the right, please enter the path to the Scenebuilder executable of your local installation. (Note: Mac Users might need to create a wrapper script that calls the executable)
  3. You can now open FXML layout files in SceneBuilder by right clicking such a file and selecting "Open with Scenebuilder".

Troubleshooting

If the above procedure is followed, no errors should appear. However, there are typical error patterns which may be hard to debug, but whose cause is easy to identify using the indicators listed in the following. Most of them are easy to identify by systematically placing breakpoints in the controller class. Use the following items also as instruction to perform systematic debugging!
  • The view is not available in the launched AF3 instance: Ensure that the registration of the view in the plugin.xml file is correct. Please pay special attention to the package and class names.
  • There is an exception when opening the JavaFX view:
    1. Ensure that the FXML layout file is placed in the same package as the controller.
    2. Ensure that the FXML file is contained in the binary version of the plugins (build.properties of the plugin).
  • NPEs occur in the controller when a layout element is accessed:
    1. Ensure that the field has the "@FXML" annotation.
    2. Ensure that the field is only accessed in the initialize method or later.
    3. Ensure that the name of the field in the controller is equivalent to the one defined in the layout file.