The one and only framework to build large JavaFX Applications!
To use this framework as part of your Maven build simply add the following dependency to your pom.xml file:
<dependency>
<groupId>com.dlsc.workbenchfx</groupId>
<artifactId>workbenchfx-core</artifactId>
<version>8.1.0</version>
</dependency>
<dependency>
<groupId>com.dlsc.workbenchfx</groupId>
<artifactId>workbenchfx-core</artifactId>
<version>11.1.0</version>
</dependency>
To use this framework as part of your gradle build simply add the following to your build.gradle file and use the following dependency definition:
dependencies {
compile group: 'com.dlsc.workbenchfx', name: 'workbenchfx-core', version: '8.1.0'
}
dependencies {
compile group: 'com.dlsc.workbenchfx', name: 'workbenchfx-core', version: '11.1.0'
}
- What is WorkbenchFX?
- Advantages
- Main Components
- Documentation
- Basic Structure
- Demos
- Getting Started
- Using the Components
- Restyling
- Team
WorkbenchFX is an out of the box solution to build large applications from multiple views, called modules. It offers you good user experience and a beautiful design.
As a developer, you often start with the views, so you can quickly show your progress to your customer. After that, some question may arise such as:
- "How do I bring all those views together?"
- "How do I build the navigation between those views?"
- "How do I establish a good user experience?"
Exactly when those questions appear, WorkbenchFX comes into play: With WorkbenchFX you can focus on designing your views and meanwhile we're building the application around them.
WorkbenchFX also scales with growing requirements. In the beginning you just want to navigate through the views, but later on you probably would want to use a menu or a toolbar. Even that is supported by WorkbenchFX and you don't have to build anything by yourself.
If you still manage to start outgrowing the workbench, you can even replace whole parts of it with your own implementations, without having to rewrite the whole workbench.
- Less error-prone
- Less code needed
- Easy to learn
- Easy to understand
- Easy to use, especially for developers which have not much experience in working with JavaFX
- A well designed, adaptable UI, inspired by the material design standards
- Multiple, independent workbench modules, displayed in Tabs combine into one great application
- The
jdk8
branch works well with JPRO - FXML & Scene Builder support
The most important components are noted in the picture and the corresponding table below:
Nr. | Component | Description |
---|---|---|
_ | WorkbenchModule |
A Workbench consists of multiple modules. It contains a title, an icon and the content to be displayed in it. It represents the views mentioned in chapter What is WorkbenchFX? |
2 | Tile |
For each WorkbenchModule a Tile will be created. Clicking on the Tile opens the corresponding module |
3 | Tab |
A Tab will be displayed for each open module. Clicking on a Tab opens and shows the view of the corresponding module. Pressing the 'x' button closes the module |
4 | Tab bar | The upper section of the window, where the Tab s of the current open modules are displayed |
5 | Add button | The button used to open a new module. It opens an overview of all available modules |
6 | AddModulePage |
Stores all the Page s on which the Tile s are displayed |
7 | Page |
When more modules are loaded than defined in the modulesPerPage() attribute, the Workbench creates multiple Page s on which the Tile s are displayed |
8 | Pagination dots | Are only displayed when having multiple Page s and can be used for navigating through them |
9 | Toolbar | It contains ToolbarItem s. If the bar does not contain any items, the toolbar will be hidden automatically |
10 | ToolbarItem |
Depending on the defined attributes, the item behaves like a JavaFX Label , Button or MenuButton (more about ToolbarItem s: ToolbarItem) |
11 | Menu button | It opens the NavigationDrawer . The position of the button varies depending on the amount of items to be displayed in the toolbar and the NavigationDrawer . If the NavigationDrawer does not contain any items, the button will not be displayed at all. If any items are in the toolbar, it will be displayed on the left side of the toolbar, otherwise on the left side of the tab bar |
Nr. | Component | Description |
---|---|---|
12 | NavigationDrawer |
It displays a logo which can be set in the stylesheet (described in chapter Setting a Logo) and the defined MenuItem s. The default hover behavior over its items can be changed using the method call navigationDrawer.setMenuHoverBehavior() . It can be closed by clicking on the GlassPane or by pressing the back arrow button |
13 | GlassPane |
The GlassPane prevents click events on the components below and adds a scrim to the background. Unless a blocking (modal) overlay is being displayed, clicking on the GlassPane closes the overlay (more about the blocking attribute: Custom Overlay, Custom Dialog) |
Nr. | Component | Description |
---|---|---|
14 | Drawer |
It is possible to use workbench.showDrawer() to show drawers with custom content. All four sides of the window are supported (more about Drawer s: Drawer) |
Nr. | Component | Description |
---|---|---|
15 | DialogControl |
Dialogs can be shown using a variety of predefined dialog types like showInformationDialog() , showErrorDialog , etc. Calling workbench.showDialog(WorkbenchDialog) shows a custom dialog (more about dialogs: Dialog) |
Nr. | Component | Description |
---|---|---|
16 | Module toolbar |
Displays the module's toolbar items (Workbench Module). The toolbar will automatically be shown as soon as there are items to be displayed and it will be hidden when there are none |
For further information about the components, refer to the Javadoc
The detailed documentation can be found in: workbenchfx-demo/src/main/resources/com/dlsc/workbenchfx/modules/webview/index.html
It can also be read by opening the Documentation module in ExtendedDemo
or the CustomDemo
.
WorkbenchFX uses the builder pattern to create the Workbench
object, since it allows to use optional features in a flexible way.
The minimal usage requires only to specify the WorkbenchModule
objects to be used in the Workbench
.
Afterwards, optional features can be defined using their respective method call before calling build()
.
For better illustration, the basic concept of creating a Workbench
object is shown below:
Workbench workbench =
Workbench.builder( // Using the static method call
new CustomWorkbenchModule() // class CustomWorkbenchModule extends WorkbenchModule
...
)
// .toolbarRight(...) // optional usage of additional features like navigationDrawer(), modulesPerPage(), etc.
.build(); // The build call creates, initializes and returns the Workbench object
Note:
- The result of the
build()
call is aControl
which can be set in a scene - For use with FXML & Scene Builder, there is also a default constructor
new Workbench()
. However, in this case, modules and other optional features need to be defined separately afterwards
The lifecycle methods are implicitly being called by the workbench.
You must not call any of the lifecycle methods by yourself.
To close and open modules use only workbench.openModule()
and workbench.closeModule()
.
The abstract class WorkbenchModule
contains four different lifecycle methods which can be overridden:
Method | Description |
---|---|
init() |
Gets called when the module is being opened from the overview for the first time |
activate() |
Gets called whenever the currently displayed content is being switched to this module |
deactivate() |
Gets called whenever this module's currently displayed content is being switched to the content of another module |
destroy() |
Gets called when this module is explicitly being closed by the user by clicking on the 'x' symbol in the Tab |
When extending WorkbenchModule
, it is only required to implement the activate()
method.
(Extending the WorkbenchModule)
Overriding all other lifecycle methods is optional and only needs to be done to perform additional actions in the lifecycle.
Besides a call to super()
, no further workbench-related code is required when overriding a lifecycle method.
Note:
- For further information, refer to our documentation or the Javadoc
- The full documentation about the module lifecycle can be found in the documentation file
workbenchfx-demo/src/main/resources/com/dlsc/workbenchfx/modules/webview/index.html
, in the section WorkbenchModule Lifecycle
We created several demos to visualize the capabilities of WorkbenchFX in the workbenchfx-demo
folder:
File | Description |
---|---|
SimpleDemo.java |
Shows the simplest usage of WorkbenchFX with only three modules and no optional features used |
ExtendedDemo.java |
Shows a simple workbench application with most of the features used in a simple way. |
CustomDemo.java |
A workbench application which uses all features, to demonstrate the full capability of WorkbenchFX |
FXMLDemo.java |
A minimal example of how to use WorkbenchFX with FXML & Scene Builder |
It is required to create a new class and extend WorkbenchModule
, in order to create a custom module:
public class CustomModule extends WorkbenchModule {
}
It is then required to call the super()
constructor and pass in a String
as the name and either an Image
, FontAwesomeIcon
or MaterialDesignIcon
as an icon for the module
(icon cheatsheets: materialdesignicons.com, fontawesome.com):
public CustomModule() {
super("My first Workbench module", MaterialDesign.MDI_THUMB_UP); // a name and an icon is required
}
Furthermore, overriding the activate()
method is also required.
This lifecycle method will be called when clicking on the Tile
to open the module (see Module Lifecycle):
@Override
public Node activate() {
return new Label("Hello World"); // return here the actual content to display
}
The minimal implementation of a custom WorkbenchModule
finally looks like the code snippet below.
Returning a Hello World Label represents the view which will be displayed in the final application.
For further information, refer to the Javadoc.
public class CustomModule extends WorkbenchModule {
public CustomModule() {
super("My first Workbench module", MaterialDesign.MDI_THUMB_UP); // A name and an icon is required
}
@Override
public Node activate() {
return new Label("Hello World"); // return here the actual content to display
}
}
After extending the WorkbenchModule
, the Workbench
can be created.
To do this, access the WorkbenchBuilder
by calling Workbench.builder()
, passing in the previously created module as an object and build the Workbench
by calling workbench.build()
:
// Creating the Workbench
Workbench customWorkbench = Workbench.builder( // Getting a WorkbenchBuilder
new CustomModule() // Adding the CustomModule
).build(); // Building the Workbench
For the final application, it can then be used in a Scene
as follows:
public class CustomDemo extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
Workbench customWorkbench = Workbench.builder(
new CustomModule()
).build();
Scene myScene = new Scene(customWorkbench);
primaryStage.setScene(myScene);
primaryStage.setWidth(700);
primaryStage.setHeight(450);
primaryStage.show();
}
}
This code snippet results in the following application:
The default implementation comes with a clickable Tile
to open the module.
Opening the module, creates a Tab
with the defined icon and text.
The content returned in the activate()
method is displayed in the center.
By clicking on the add button, you can get back to the AddModulePage
.
Closing the opened module is achieved through clicking on the close button in the Tab
.
If the workbench consists of only one module, a single module layout is used to display the module.
In the single module layout, the tab bar and the add module button are not visible and the module is
automatically opened on startup.
This results in a very basic application as can be seen below
These optional method calls are called after adding the custom modules to the builder:
Workbench workbench = Workbench.builder(...)
.modulesPerPage(6) // call the optional methods
.build();
The following methods are optionally available to further configure the Workbench
:
Method in WorkbenchBuilder | Description |
---|---|
modulesPerPage() |
Defines the amount of Tile s that should be shown per Page in AddModulePage . The default value is set to 6 |
navigationDrawerItems() |
Allows to add MenuItem s which are then displayed in the NavigationDrawer . |
toolbarLeft() |
Allows to add ToolbarItem s on the left side of the toolbar on top of the Tab s |
toolbarRight() |
Allows to add ToolbarItem s on the right side of the toolbar on top of the Tab s |
When the default layout of Page
, Tab
, Tile
or NavigationDrawer
don't fulfill the desired requirements, it is possible to replace them:
Method in WorkbenchBuilder | Description |
---|---|
navigationDrawer() |
Allows setting a custom implementation of the NavigationDrawer control, which will then be used |
pageFactory() |
Requires a Callback function which takes a Workbench and then returns a custom implementation of a Page control |
tabFactory() |
Requires a Callback function which takes a Workbench and then returns a custom implementation of a Tab control |
tileFactory() |
Requires a Callback function which takes a Workbench and then returns a custom implementation of a Tile control |
After the build()
call on WorkbenchBuilder
, the Workbench
is created.
The following selective calls might be of interest:
Method in Workbench | Description |
---|---|
showNavigationDrawer() |
Shows the NavigationDrawer |
getNavigationDrawer() |
Returns the NavigationDrawer |
getNavigationDrawerItems() |
Returns the ObservableList of the drawer's ToolbarItem s |
openModule() |
Opens the specified module |
getModules() |
Returns a list of all modules stored in the workbench |
show...Dialog() |
Shows a predefined dialog |
showDialog() |
Shows a custom dialog |
showDrawer() |
Shows a custom drawer |
getToolbarControlsLeft() |
Returns the list of items on the left of the Toolbar |
getToolbarControlsRight() |
Returns the list of items on the right of the Toolbar |
showOverlay() |
Shows a custom overlay |
hideOverlay() |
Hides a custom overlay |
The WorkbenchModule
also provides useful functionality.
It is possible to add ToolbarItem
s to the toolbar of the module (just like in the workbench):
Method (WorkbenchModule) | Description |
---|---|
getWorkbench() |
In the init() call, the Workbench is stored in the module. Calling this enables to call methods on the Workbench within the WorkbenchModule . |
getToolbarControlsLeft() |
Returns a list of ToolbarItem s. Adding items to the list will automatically create a toolbar between the tab bar and the module content and show the items on the left side |
getToolbarControlsRight() |
Returns a list of ToolbarItem s. Adding items to the list will automatically create a toolbar between the tab bar and the module content and show the items on the right side |
close() |
Will immediately close the module, without calling destroy() first (see Module Lifecycle) |
The ToolbarItem
s which can be set in the toolbars of either the workbench or the module are styled and behave differently based on their content.
If for example the item contains a String
as text and a MenuItem
it is automatically assumed that the styling and behavior of a MenuButton
is needed.
If on the other hand only an IconView
is defined, it is assumed, the behavior of a Label
is desired.
Adding different attributes to the ToolbarItem
results in different representations:
They can also be seen in the toolbar of the CustomDemo
A demo of the dialogs can be found in the DialogTestModule
of the Custom Demo
WorkbenchFX comes with a lot of predefined dialog types.
Using them is as simple as calling workbench.show...Dialog()
with the desired dialog type.
After clicking on one of the Button
s of a dialog, the corresponding ButtonType
is returned as the result of the dialog.
Therefore it is required to define a Consumer<ButtonType>
for every dialog to validate the answer.
A few examples on how to use them are listed below:
// Precondition
Workbench workbench = Workbench.builder(...).build; // Creating the workbench
Button dialogBtn = new Button("Show Dialog"); // Assuming the button is used in a module
Sometimes just using the default dialog types are not enough.
For such special cases, the workbench.showDialog()
method can be used.
With WorkbenchDialog.builder()
a custom dialog can be created.
The builder provides some useful methods which can be used:
WorkbenchDialog.builder(Parameters) | Description |
---|---|
String title |
Required and defines the title of the dialog |
String message |
Optionally either message or content can be defined. The message is located below the title |
Node content |
A Node with custom content |
Type type |
Defines one of the default dialog types like Type.ERROR , Type.INFORMATION , etc. The corresponding buttons and style class will automatically be set |
ButtonType... buttonTypes |
All the specified button types will be set (eg. OK , CANCEL and APPLY for a preferences dialog) |
Note:
- Defining
content
will override further definitions ofmessage
,details
orexception
WorkbenchDialog.builder().Parameters | Description |
---|---|
blocking(boolean) |
Defines whether clicking on the GlassPane closes the dialog or not (i.e. forcing a decision when blocking) |
onResult(Consumer<ButtonType>) |
After clicking on a dialog button, the clicked ButtonType is returned. Enables to define an action to be performed when a dialog button was pressed |
details(String) |
The dialog will display the specified error message (Error Dialog With Details). Will not be displayed when defining a content |
exception(Exception) |
The dialog will display the stacktrace of a specified Exception (Error Dialog With Exception). Will not be displayed when defining a content |
maximized(boolean) |
Defines whether the dialog's size should take up the whole window or only as much as needed by the content |
showButtonsBar(boolean) |
Defines whether the dialog's buttons should be shown or not |
onShown(EventHandler<Event>) |
The EventHandler which is called when the dialog is showing |
onHidden(EventHandler<Event>) |
The EventHandler which is called when the dialog is hidden |
dialogControl(DialogControl) |
Makes it possible to define a custom DialogControl |
build() |
Builds the WorkbenchDialog |
WorkbenchDialog | Description |
---|---|
getButton(ButtonType buttonType) |
Returns an Optional<Button> of the dialog. Useful when accessing the buttons of the dialog is needed |
Using the builder it is possible to write some interesting custom dialogs:
// Precondition
Workbench workbench = Workbench.builder(...).build; // Creating the workbench
Button dialogBtn = new Button("Show Dialog"); // Assuming the button is used in a module
Other examples can be found in the DialogTestModule
of the Custom Demo
In some cases it is necessary to prevent a module from closing. For example, the following dialog asks about saving before closing:
As mentioned in Module Lifecycle, the destroy()
method will be called when closing the module.
The module will be closed as soon as the destroy()
method returns true
.
If you want to prevent the module from closing, return false
and then close the module by calling close()
, as soon as you are ready.
The code snippet below results in the dialog displayed in the image on top:
@Override
public boolean destroy() {
// Perform an asynchronous task (in our case showing a dialog)
getWorkbench().showDialog(WorkbenchDialog.builder(
"Save before closing?",
"Do you want to save your progress? Otherwise it will be lost.",
ButtonType.YES, ButtonType.NO, ButtonType.CANCEL)
.blocking(true)
.onResult(buttonType -> {
// If CANCEL was not pressed
if (!ButtonType.CANCEL.equals(buttonType)) {
if (ButtonType.YES.equals(buttonType)) {
// YES was pressed -> Proceed with saving
...
}
close(); // Close the module since CANCEL was not pressed
}
})
.build());
return false; // return false, because we're closing manually
}
Calling workbench.showDrawer()
, enables you to show a custom drawer (like the NavigationDrawer
).
There are two possibilities for showing a drawer:
workbench.showDrawer(
Region drawer, // Drawer to be shown
Side side // From which side the drawer should come from
);
Calling this, the width of the drawer is calculated automatically and takes the width which fits best for the drawer content defined. The other possibility comes into action when a specific width is desired:
workbench.showDrawer(
Region drawer, // Drawer to be shown
Side side // From which side the drawer should come from
int percentage // Defines how much of the screen should be covered
);
The percentage
can be defined in an Integer
range between 0 and 100.
It represents the percentage of the window the drawer covers when showing.
Examples of drawers can be found in the DrawerTestModule
of the Custom Demo
The foundation of Dialogs and Drawers are overlays.
It is possible to define a custom overlay by calling workbench.showOverlay()
.
The defined overlay will be stacked on top of a GlassPane
.
workbench.showOverlay(
Region overlay, // Overlay to be shown
boolean blocking // true, if the overlay should not be closed when clicking on the GlassPane
);
The overlay can essentially be any Region
(for example a custom Control
).
Per default, the defined content will be displayed in the top-left corner of the window.
To center the content of an overlay, perform the following call on the overlay:
StackPane.setAlignment(overlay, Pos.CENTER); // is needed to center the overlay on the screen
First of all: WorkbenchFX does not interfere with the styles of the individual modules. This way each module can be styled independently and you do not have to worry about the workbench influencing the styling.
But it is possible to alter the styles of the workbench itself. WorkbenchFX comes with an out of the box styling. It is strongly inspired by Material Design.
The workbench's styling can be altered by referencing a stylesheet, like in the Custom Demo:
workbench.getStylesheets().add(CustomDemo.class.getResource("customTheme.css").toExternalForm());
In the file customTheme.css
, some default colors are referenced:
* {
-primary-color: #6200EE;
-primary-variant-color: #3700b3;
-secondary-color: #6300ff;
-secondary-variant-color: #1e005f;
-background-color: #FFFFFF;
-surface-color: #FFFFFF;
-error-color: #B00020;
-on-primary-color: #FFFFFF;
-on-secondary-color: #FFFFFF;
-on-background-color: #000000;
-on-surface-color: #000000;
-on-error-color: #FFFFFF;
}
.logo {
/* Reference to the applications logo */
-fx-graphic: url("logo.png");
}
The colors are named according to the Material Design guidelines.
Changing those colors leads to a complete restyling of the workbench.
For example, a file darkTheme.css
is also referenced in the demo and leads to the following result:
If you want to change the colors of the application, create a new css file customTheme.css
and add it to the workbench:
workbench.getStylesheets().add(CustomDemo.class.getResource("customTheme.css").toExternalForm());
In context, the code looks like this:
public class CustomDemo extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
Workbench customWorkbench = Workbench.builder(
new CustomModule()
).build();
// Adding the stylesheet to the workbench to restyle it
customWorkbench.getStylesheets().add(CustomDemo.class.getResource("customTheme.css").toExternalForm());
Scene myScene = new Scene(customWorkbench);
primaryStage.setScene(myScene);
primaryStage.setWidth(700);
primaryStage.setHeight(450);
primaryStage.show();
}
}
Changing the colors in the css
file to something like this:
* {
-primary-color: #9db668;
-primary-variant-color: #7f975f;
-secondary-color: #9db668;
-secondary-variant-color: #7f975f;
-background-color: #FFFFFF;
-surface-color: #FFFFFF;
-error-color: #B00020;
-on-primary-color: #FFFFFF;
-on-secondary-color: #FFFFFF;
-on-background-color: #747474;
-on-surface-color: #747474;
-on-error-color: #FFFFFF;
}
Leads to following design:
In the upper section of the NavigationDrawer
, there is a section for a logo.
The logo is defined in the custom stylesheet (how to create one and reference it is described in the previous chapter).
An example implementation of the logo can be found in the customTheme.css
of the Custom Demo:
.logo {
/* Reference to the application's logo */
-fx-graphic: url("logo.png");
}
Changing the logo can easily be done by adding an image with the correct size in the resources
folder and referring to its name in the stylesheet:
.logo {
/* Reference to the applications logo */
-fx-graphic: url("myCustomLogo.png"); /* Replacing logo.png with a different image. */
}
Note:
- WorkbenchFX does not resize the image. We suggest a maximum image height of 250px
Sometimes just changing the colors is not enough.
Every component in the workbench has its own .class
or #id
.
This way, the components can be restyled if needed.
For example, every generated Tab
and Tile
has its own unique #id
.
The naming conventions for the #id
are defined as:
- Prefix:
tab
/tile
(depending on the component) - Body: the name of the module
- all special characters besides hyphens are removed
- all spaces are replaced by hyphens (
-
) - uppercase letters are converted to lowercase
Setting the LOGGER level to debug will print each module's tab and tile id as soon as they are set:
- Set Tab-ID of '(MODULE NAME)' to: '(TAB ID)'
- Set Tile-ID of '(MODULE NAME)' to: '(TILE ID)'
For further information, refer to the Javadoc of WorkbenchUtils.convertToId()
#id
example:
Module name:
François' Module
Results in:
tab-franois-module // Tab id
tile-franois-module // Tile id
LOGGER output:
Set Tab-ID of 'François' Module' to: 'tab-franois-module'
Set Tile-ID of 'François' Module' to: 'tile-franois-module'
Starting with the result after chapter Getting Started:
Assuming the Tab
and Tile
need to be restyled, add the following code snippet to the customTheme.css
file:
/* Styling the Tile */
#tile-my-first-workbench-module .tile-box {
-fx-background-color: -primary-color !important; /* The background of the Tile */
}
#tile-my-first-workbench-module .tile-box .text, /* The icon and the text */
#tile-my-first-workbench-module .tile-box .glyph-icon {
-fx-fill: -on-primary-color !important;
}
/* Styling the Tab */
#tab-my-first-workbench-module:selected {
-fx-background-color: #747474 !important; /* The background of the Tab */
-fx-background-radius: 5px 5px 0 0 !important;
}
#tab-my-first-workbench-module:selected .text, /* The icon and the text */
#tab-my-first-workbench-module:selected .glyph-icon {
-fx-fill: #ffffff !important;
}
#tab-my-first-workbench-module:selected .shape {
-fx-background-color: #ffffff !important; /* The close icon */
}
Leads to following styling:
Note:
- The css color variables can still be used in the
customTheme.css
file - Since the styling of the workbench is more specific, the
!important
tag is required when styling the workbench - A tool like ScenicView helps to determine the style classes
-
Marco Sanfratello
- marco.sanfratello@students.fhnw.ch
- Skype: sanfratello.m@gmail.com
- GitHub: Genron
-
François Martin
- francois.martin@students.fhnw.ch
- Skype: francoisamimartin
- GitHub: martinfrancois
-
Dirk Lemmermann
- dlemmermann@gmail.com
- Skype: dlemmermann
- GitHub: dlemmermann
-
Dieter Holz
- dieter.holz@fhnw.ch
- Skype: dieter.holz.canoo.com
- GitHub: DieterHolz