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: 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:
- Create a new class for the view’s controller extending
CompositeFXControllerBase
. - If the view is supposed contain other
IComponentFXController
s, pass them to thesuper
constructor (otherwise call it without any argument). - Declare the name of the FXML file by overriding the
getFXMLLocation()
. - Specify how to initialize the view and how to add the given
containments (if there are any) in the
initialize(ICompositeFXController[] containments)
method. - 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-mostICompositeFXController
to thesuper
constructor. This class can then be registered in aplugin.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.
- Navigate to the
playground
package in thesrc
folder of theorg.fortiss.af3.exploration.ui
plugin.*
- Create a new class, called
HelloWorldFXController.java
, deriving fromCompositeFXControllerBase
. The IDE will then either tell you to implement the parent’s abstract methods or it will create stubs for thegetFXMLLocation
andinitialize
methods. - 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!).
- 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 readreturn "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
}
}
-
now navigate to the same
playground
package within the same plugin - but this time in theres
folder! once there, create a newhelloworldlayout.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:
-
in the scenebuilder drag and drop a
pane
in the preview window and resize it as you want. then do the same with alabel
, 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. -
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 thehelloworldfxcontroller.java
is located (back in thesrc
folder) and create another class called “helloworldfxviewpart.java”, this time deriving fromaf3fxviewpart
. now add a constructor (eclipse will force you anyways), and remove all parameters. it is only needed to contain a call to thesuper
constructor passing an instance of thehelloworldfxcontroller
(as we don’t have css style sheets, the second parameter can benull
).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>
- 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:
- open the
helloworldfxviewpart.java
and instead of passing an instance of thehelloworldfxcontroller
to the super constructor, we now pass a new instance of theexplorationuifxcontroller
- to which we pass the new instance of thehelloworldfxcontroller
as well as a title we want to show in the header bar e.g. “hello world!”. this waysuper(new helloworldfxcontroller(), null);
becomes
<code class="java">super(new explorationuifxcontroller(new helloworldfxcontroller(), "hello world!"), null);
- 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:
- open the
helloworldlayout.fxml
using the scenebuilder:- add a
textfield
with thefx:id
set tonametextfield
. the respective setting is located in thecode
tab of the right accordion. - add a
button
with thefx:id
set tosubmitbutton
. in the samecode
tab where you set thefx:id
look for theonaction
option and set it toonsubmit
. - delete the text of the “hello world!” label and set its
fx:id
todisplaylabel
. - don’t forget to save your changes!
- add a
- now switch back to eclipse to implement some actions:
-
open the
helloworldfxcontroller
class. -
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 theirfx: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 “…”:
<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.