|
|
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 >
|
|
|
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.
|
|
|
|