Building_a_view, version 1 authored by Tiziano Munaro's avatar Tiziano Munaro
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:
![](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:
attachment: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:
- attachment:HelloWorldLayout.fxml
- attachment:HelloWorldFXController.java
- attachment: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:
```java">
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`).
<code class="java">
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:
<code class="xml"><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
<code class="java">super(new helloworldfxcontroller(), null);
```
becomes
<code class="java">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`!
<code class="java">
/** {@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 “…”:
<code class="java">
/** {@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:
<code class="java
/** 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 &gt;
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.