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: ![](pictures/composition_illustration.png) 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](presentations/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: - [HelloWorldLayout.fxml](code/HelloWorldLayout.fxml) - [HelloWorldFXController.java](code/HelloWorldFXController.java) - [HelloWorldFXViewPart.java](code/HelloWorldFXViewPart.java) #### 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 u make sure that your file and folder structure looks like this by now: ![](hello-world_files-and-folders.png) 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: 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](https://www.eclipse.org/efxclipse/index.html) 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](http://gluonhq.com/products/scene-builder/) 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. Ensure that the field is only accessed in the `initialize@ method or later. 2. Ensure that the name of the field in the controller is equivalent to the one defined in the layout file.