-
Notifications
You must be signed in to change notification settings - Fork 19
Home
- What is CustomStage?
- For whom?
- Benifits of using CustomStage
2) Installing
3) Examples
- Basic usage
- Transparent Stage
- Changing Scenes
- Importing stylesheet to decorate the CustomStage
- Using custom icons for close,minimize and maximize/restore
- Title-bar customizing and setting application icon
- Using Navigation panes
- Additional Tools
- A complete implementation
Everytime a JavaFx application is developed with
stage.initStyle(StageStyle.UNDECORATED)
resulting an Undecorated stage, there is a common issue that every developer has to be consirned with, which is creating a proper window which would be resposive, resizable, with native close,minimize, maximize/restore action button behaviour.
CustomStage is a JavaFx Undecorated Stage which includes the native behaviour of the default JavaFx Decorated Stage and is fully stylable. So CustomStage reduces the time cost in developing the undecorated window but the actual application.
Are you a developer who needs to focus less time developing your window, and want to focus more time on your application itself?
You need an already-built Undecorated window with all the native functionalities but also need to customize it the way you need?
-
Native behaviour of a window
- Resizability
- Window draggability
- Responsiveness
- Close,minimize,maximize/restore button behaviour
- Title bar
- Icon on task bar
-
Stylable the way you want it to be
- Style using methods
- Load a stylesheet
-
Window aero snaps
-
Built-in navigation panes (Static navigation and Drawers)
- Place navigation panes on Top/left/right/bottom side of the window
- Built-in open/hide functionalities for Drawers (automatically detects whether the hide/open operation needs to be called)
-
Change the default icons for close,minimize,maximize/restore buttons
-
Change close,minimize,maximize/restore buttons' hover color
And many more ...
Starting from version 1.3.1 CustomStage releases are/will be available through JCenter and MavenCentral
<dependency>
<groupId>lk.vivoxalabs.customstage</groupId>
<artifactId>CustomStage</artifactId>
<version>1.3.2</version>
</dependency>
dependencies {
compile 'lk.vivoxalabs.customstage:CustomStage:1.3.2'
}
- v1.3.1 CustomStage via JitPack (See the releases here : https://jitpack.io/#Oshan96/CustomStage)
Add jitpack as a repository
repositories {
maven { url 'https://jitpack.io' }
}
Add dependancy
dependencies {
compile 'com.github.Oshan96:CustomStage:v1.3.1'
}
Add jitpack as a repository
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Add dependancy
<dependency>
<groupId>com.github.Oshan96</groupId>
<artifactId>CustomStage</artifactId>
<version>v1.3.1</version>
</dependency>
Or Download from releases and add the jar file as a dependency to your project
Here are some examples for using CustomStage
Important : CustomStageBuilder class object is used to construct objects of CustomStage.
CustomStageBuilder builder = new CustomStageBuilder();
builder = builder.setWindowTitle("CustomStage example");
builder = builder.setTitleColor("white");
builder=builder.setWindowColor("blue"); //color can be name, hex or rgb value
CustomStage stage = builder.build();
stage.show();
Or from method chaining ..
CustomStage stage = new CustomStageBuilder()
.setWindowTitle("CustomStage example") //Sets the title
.setTitleColor("rgb(255,255,255)") //Color of title text
.setWindowColor("#5675FA") //Color of the window
.build();
stage.show();
Complete Source code :
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import lk.vivoxalabs.customstage.CustomStage;
import lk.vivoxalabs.customstage.CustomStageBuilder;
public class StageTest extends Application{
public static void main(String args[]){
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
CustomStage stage = new CustomStageBuilder()
.setWindowTitle("CustomStage example")
.setTitleColor("#FFFFFF")
.setWindowColor("rgb(34,54,122)")
.build();
stage.show();
}
}
CustomStage transparent_stage = new CustomStageBuilder()
.setWindowTitle("CustomStage Transparent") //Sets the title
.setTitleColor("#FFFFFF") //Color of title text
.setWindowColor("rgba(125,233,153,0.6)") //Color of the window (used alpha range to make transparent)
.build();
transparent_stage.show();
Complete source code
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import lk.vivoxalabs.customstage.CustomStage;
import lk.vivoxalabs.customstage.CustomStageBuilder;
public class StageTest extends Application{
public static void main(String args[]){
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
CustomStage transparent_stage = new CustomStageBuilder()
.setWindowTitle("CustomStage Transparent") //Sets the title
.setTitleColor("#FFFFFF") //Color of title text
.setWindowColor("rgba(125,233,153,0.6)") //Color of the window (used alpha range to make transparent)
.build();
transparent_stage.show();
}
}
CustomStage stage = new CustomStageBuilder()
.setWindowTitle("Custom Icons") //Sets the title
.setTitleColor("#FFFFFF") //Color of title text
.setWindowColor("#7D3C98") //Color of the window
.setActionIcons(new Image("btnClose.png"),
new Image("btnMinimize.png"),
new Image("btnMaximize.png"),
new Image("btnRestore.png"))
.build();
stage.show();
Complete source code (Assume "btnClose","btnMinimize","btnMaximize" and "btnRestore" are PNG files stored in the same directory)
import javafx.application.Application;
import javafx.scene.image.Image;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import lk.vivoxalabs.customstage.CustomStage;
import lk.vivoxalabs.customstage.CustomStageBuilder;
public class StageTest extends Application{
public static void main(String args[]){
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
CustomStage stage = new CustomStageBuilder()
.setWindowTitle("Custom Icons") //Sets the title
.setTitleColor("#FFFFFF") //Color of title text
.setWindowColor("#7D3C98") //Color of the window
.setActionIcons(new Image("btnClose.png"),
new Image("btnMinimize.png"),
new Image("btnMaximize.png"),
new Image("btnRestore.png"))
.build();
stage.show();
}
}
IMPORTANT : Use CustomStage-v1.2.2 and later for this feature Note : Assume "icon.png" image is located in the same directory
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#2F75AF") //Color of the window
.setWindowIcon("icon.png") //set icon.png as application's icon
.setWindowTitle("Customized title-bar",HorizontalPos.LEFT,HorizontalPos.CENTER) // Set the title as "Customized title-bar", postions close,minimize,maximize buttons to the left side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("white")
.build();
stage.show();
Complete source code (Assume "icon.png" is in the same directory as "StageTest" class)
import javafx.application.Application;
import javafx.scene.image.Image;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import lk.vivoxalabs.customstage.CustomStage;
import lk.vivoxalabs.customstage.CustomStageBuilder;
import lk.vivoxalabs.customstage.tools.HorizontalPos;
public class StageTest extends Application{
public static void main(String args[]){
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#2F75AF") //Color of the window
.setIcon("icon.png") //set icon.png as application's icon
.setWindowTitle("Customized title-bar",HorizontalPos.LEFT,HorizontalPos.CENTER) // Set the title as "Customized title-bar", postions close,minimize,maximize buttons to the left side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("white")
.build();
stage.show();
}
}
For a window, it is always easier if there are in-built navigation panes provided. Well, CustomStage does. There are two basic types of navigation panes included for four locations of the window (Top,Left,Right,Bottom)
- Static navigation panes - Navigation panes which are always visible in the given location.
- Dynamic navigation panes - These are drawers provided as navigation panes. These dynamic navigation can either be shown or hidden for a given event.
For both these navigation types, the location of the navigation pane (where it should be located in the window) can be given using NavigationType
enum.
Navigation Pane Location | Enum | Description |
---|---|---|
Top | NavigationType.TOP | Placed below the title bar and takes the width of the window |
Bottom | NavigationType.BOTTOM | Placed at the bottom of the window and takes the width of the window |
Left | NavigationType.LEFT | Placed at the left side of the window. Takes the height of the window (- height of title-bar) |
Right | NavigationType.RIGHT | Placed at the right side of the window. Takes the height of the window (- height of title-bar) |
From a CustomStageBuilder object, setNavigationPane()
method is used to set a navigation pane to the stage. There are 3 different setNavigationPane()
methods are provided in the CustomStageBuilder class.
The three methods looks like following.
setNavigationPane(type,navigationPane)
setNavigationPane(style,type,navigationPane)
setNavigationPane(style,type,navigationPane,verticalSpace,horizontalSpace,isSpaceDivided)
Following table briefs each parameter of the 3 methods.
Parameter | Data type | Description |
---|---|---|
type | NavigationType | Defines where the navigation pane should be placed within the window (Top,Bottom,Left,Right) |
navigationPane | Pane | The pane which should be used as the navigation pane |
style | Style | Style.DYNAMIC and Style.SATATIC determines whether the navigation pane should be created static or dynamic |
verticslSpace | double | Applies only for dynamic navigations (Style.DYNAMIC ) which are placed on the left/right side of the window for "type" parameter (NavigationType.LEFT / NavigationType.RIGHT ). This value determines whether a space should be left (without taking the whole height of the window for the navigation pane). The given value will be counted in pixels and will be reduced from the height of the navigation pane.This value will be ignored for static navigations and for dynamic navigations which are placed at top or bottom of the window. |
horizontalSpace | double | Applies only for dynamic navigations (Style.DYNAMIC ) which are placed on the top/bottom side of the window for "type" parameter (NavigationType.TOP / NavigationType.BOTTOM ). This value determines whether a space should be left (without taking the whole width of the window for the navigation pane). The given value will be counted in pixels and will be reduced from the width of the navigation pane.This value will be ignored for static navigations and for dynamic navigations which are placed at left or right side of the window. |
isSpaceDivided | boolean | Determines whether (if true ) the given verticalSpace/horizontalSpace should be divided from top and bottom side of the window (for left/right navigations) or from left and right side of the window (for top/bottom navigations). By default (if this value is false ), for left/right navigations, the given verticalSpace value is consumed from the top of the window (+ height of the title-bar) and for top/bottom navigations, the given horizontalSpace value is consumed by the left side of the navigation pane. |
NOTE : For static navigation panes, using
setNavigationPane(type,navigationPane)
method is enough since the other two methods focus more on dynamic navigation panes.
For the static navigation implemetation examples,
setNavigationPane(type,navigationPane)
method will be used here.
- Left static navigation
AnchorPane navigationPane = new AnchorPane(new Button("Click Me on Left!"));
navigationPane.setStyle("-fx-background-color: red;");
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#F4A576") //Color of the window
.setWindowTitle("Left static navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Left static navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("black")
.setNavigationPane(NavigationType.LEFT,navigationPane)
.build();
stage.show();
- Right static navigation
AnchorPane navigationPane = new AnchorPane(new Button("Click Me on Right!"));
navigationPane.setStyle("-fx-background-color: red;");
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#F4A576") //Color of the window
.setWindowTitle("Right static navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Right static navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("black")
.setNavigationPane(NavigationType.RIGHT,navigationPane)
.build();
stage.show();
- Top static navigation
AnchorPane navigationPane = new AnchorPane(new Button("Click Me on Top!"));
navigationPane.setStyle("-fx-background-color: red;");
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#F4A576") //Color of the window
.setWindowTitle("Top static navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Top static navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("black")
.setNavigationPane(NavigationType.TOP,navigationPane)
.build();
stage.show();
- Bottom static navigation
AnchorPane navigationPane = new AnchorPane(new Button("Click Me on Bottom!"));
navigationPane.setStyle("-fx-background-color: red;");
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#F4A576") //Color of the window
.setWindowTitle("Bottom static navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Bottom static navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("black")
.setNavigationPane(NavigationType.Bottom,navigationPane)
.build();
stage.show();
For the dynamaic navigation implemetation examples,
setNavigationPane(style,type,navigationPane,verticalSpace,horizontalSpace,isSpaceDivided)
method will be used since it covers all the posibilities of setting a dynamic navigation pane.
CustomDrawer is a modified StackPane included bundled with CustomStage, which has the basic behaviour of a Drawer.
See the API Documentation of CustomDrawer for more details here
CustomDrawer is in-built with CustomStage with additional features which are :
- An event (
dynamicDrawerEvent(NavigationType type)
) is implemented within CustomStage class and can be called through a CustomStage object to hide/open navigation pane inside it when called, so it does not require to call open or hide events seperately of CustomDrawer (See the definition here) - Set to top/bottom/left/right side of the window.
- Drawers can be used for the locations (top,bottom,left,right) of the window and each drawer's
dynamicDrawerEvent(NavigationType type)
can be called seperately.
This method shall be called through the CustomStage object if a dynamic navigationPane is in use. Will call either drawer.open() or drawer.hide() method depending on the drawer's current showing state.
How to use
For an instance, if a dynamic navigation pane has been set for the left side of the window, and you need to set an action event on a Button (and the Button is called "btn1") when it is clicked, if that navigation pane on the left side of the window is open, it should be closed. Else, it should be open. This action event can be set using the dynamicDrawerEvent as following.
Note : "stageObject" is the CustomStage object.
btn1.setOnAction(e-> {
stageObject.dynamicDrawerEvent(NavigationType.LEFT);
}
);
Hint : If the CustomStage object cannot be accessed inside the class, it can still be accessed through the event object and the event can be called like,
btn1.setOnAction(e-> {
CustomStage stageObject = ((CustomStage)((Button)e.getSource()).getScene().getWindow());
stageObject.dynamicDrawerEvent(NavigationType.LEFT);
}
);
In this dynamicDrawerEvent, NavigationType points out which drawer of the window needs to be called for open/hide event. So, it is possible for one CustomStage window to use more than one dynamic navigation drawer and call events for each drawer separately.
If there is no such navigation drawer (dynamic) for the pointed location (through NavigationType parameter of the dynamicDrawerEvent method), this method will throw an exception.
Ex : If the stage has only a TOP navigation pane (dynamic), then calling stageObject.dynamicDrawerEvent(NavigationType.LEFT);
will throw an exception. Since the stage only contain a TOP navigation, then the correct drawer event should be stageObject.dynamicDrawerEvent(NavigationType.TOP);
.
Following codes demonstrate the usage of dynamic navigation panes and dynamicDrawerEvent.
Note : The dynamicDrawerEvents are called when the window is clicked.
- Left dynamic navigation pane
AnchorPane navigationPane = new AnchorPane();
navigationPane.setStyle("-fx-background-color: purple;");
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#E46780") //Color of the window
.setWindowTitle("Left dynamic navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Left dynamic navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("white")
.setNavigationPane(Style.DYNAMIC,NavigationType.LEFT,navigationPane,0,0,false) // Dynamic Left drawer with maximum height of the window (exclude the height of title-bar)
.build();
stage.show();
stage.getScene().getRoot().setOnMouseClicked(e->stage.dynamicDrawerEvent(NavigationType.LEFT));
- Left dynamic navigation with spacing
In this example, a Button in the root pane is used to call the dynamicDrawerEvent.
AnchorPane navigationPane = new AnchorPane(); //Pane used as the navigation
navigationPane.setStyle("-fx-background-color: purple;");
Button btnAction = new Button("Trigger Drawer Event!");
HBox rootBox = new HBox(btnAction); //Hbox used as the scene of the window
rootBox.setAlignment(Pos.CENTER);
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#E46780") //Color of the window
.setWindowTitle("Left dynamic navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Left dynamic navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("white")
.setNavigationPane(Style.DYNAMIC,NavigationType.LEFT,navigationPane,50,0,false) // Dynamic Left drawer with 50px space from the top of the window (exclude the height of title-bar)
.build();
stage.show();
//Set button action to call dynamicDrawerEvent on click
btnAction.setOnAction(e->{
stage.dynamicDrawerEvent(NavigationType.LEFT);
});
//Set rootBox(HBox) as the new scene of the window
stage.changeScene(rootBox);
Right dynamic navigation pane can be created and drawer event can be called in the same manner.
- Top dynamic navigation with spacing from both sides (left and right)
AnchorPane navigationPane = new AnchorPane(); //Pane used as the navigation
navigationPane.setStyle("-fx-background-color: purple;");
Button btnAction = new Button("Trigger Drawer Event!");
HBox rootBox = new HBox(btnAction); //Hbox used as the scene of the window
rootBox.setAlignment(Pos.CENTER);
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#E46780") //Color of the window
.setWindowTitle("Top dynamic navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Left dynamic navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("white")
.setNavigationPane(Style.DYNAMIC,NavigationType.TOP,navigationPane,0,60,true) // Dynamic Top drawer with 30px space from the left and 30px space from right of the window (60px total horizontal space)
.build();
stage.show();
//Set button action to call dynamicDrawerEvent on click
btnAction.setOnAction(e->{
stage.dynamicDrawerEvent(NavigationType.TOP);
});
//Set rootBox(HBox) as the new scene of the window
stage.changeScene(rootBox);
- Using Multiple navigation panes
AnchorPane leftNav = new AnchorPane(); //Pane used as left the navigation
leftNav.setStyle("-fx-background-color: purple;");
AnchorPane bottomNav = new AnchorPane(); //Pane used as bottom the navigation
bottomNav.setStyle("-fx-background-color: navy;");
Button leftAction = new Button("Trigger left Drawer Event!");
Button bottomAction = new Button("Trigger bottom Drawer Event!");
VBox rootBox = new VBox(10,leftAction,bottomAction); //Vbox used as the scene of the window
rootBox.setAlignment(Pos.CENTER);
CustomStage stage = new CustomStageBuilder()
.setWindowColor("#E46780") //Color of the window
.setWindowTitle("Multiple dynamic navigation",HorizontalPos.RIGHT,HorizontalPos.CENTER) // Set the title as "Left dynamic navigation", postions close,minimize,maximize buttons to the right side of the title-bar and set the title to the center of the title-bar.
.setTitleColor("white")
.setNavigationPane(Style.DYNAMIC,NavigationType.LEFT,leftNav,50,0,false) // Dynamic LEFT drawer with 50px space from the top of the window
.setNavigationPane(Style.DYNAMIC,NavigationType.BOTTOM,bottomNav,0,60,true) // Dynamic LEFT drawer with 30px space from the left and 30px space from right side of the window
.build();
stage.show();
//Set button action to call dynamicDrawerEvent on click for left navigation
leftAction.setOnAction(e->{
stage.dynamicDrawerEvent(NavigationType.LEFT);
});
//Set button action to call dynamicDrawerEvent on click for bottom navigation
bottomAction.setOnAction(e->{
stage.dynamicDrawerEvent(NavigationType.BOTTOM);
});
//Set rootBox(HBox) as the new scene of the window
stage.changeScene(rootBox);
Apart from the stylable window CustomStage provides. This library consists of some additonal tools which are very helpful in developing JavaFx applications.
FileLoader is a tool provided within CustomStage framework, which can be used to load files from a given directory into memory. Basically, this FileLoader class requires two main inputs from the user. 1) Extention of the type of files needs to be loaded 2) Directory which the files need to be loaded from
Following table explains the provided constructor paramaters for the FileLoader class.
Parameter | Data Type | Description |
---|---|---|
ext | String | The extention of the type of files which are need to be fetched and loaded |
dir | String | The directory path which the files need to be loaded from |
dir | URL | A URL of a file which represents the directory which the other files need to be loaded from (This is the recommended, easiest and most accurate way to retrieve files) |
manager | SceneManager | The SceneManager object which will map the loaded files (for FXML files only) |
Example : If you need to load all the FXML files inside your "/resources" directory, then you can use the FileLoader like this :
FileLoader fileLoader = new FileLoader(getClass.getResource("/path-to-resource/MyFile.fxml"),"fxml");
This will create a FileLoader object which is eligible to load all the FXML files which are inside the same directory as "MyFile.fxml" file.
Following is an in-detail explanation of all the constructors in FileLoader class.
Creating a FileLoader object using this constructor will make the FileLoader object eligible to load the defined type of files (of the extention given using "ext" paramter) from the same directory as the Caller Class.
Ex : If the FileLoader object was created within Foo.class, and the "ext" was "fxml", then this will create a FileLoader object which is eligible to load all the FXML files within the same directory as Foo.class .
However, this constructor shall not be used in development environment. Plus, this constructor uses StackTrace to catch the caller class and this has been created solely to be used through a SceneManager object (Read the definition in FileLoader API DOC why). Even so, it is still not recommended to be used will cause trouble, thus usage of this constructor in any way is deprecated.
This constructor is used to create a FileLoader object which is eligible to load the given type of files through "ext" which defines the file-extention from the directory which the path of it is represnted in String format using "dir" paramter. The String "dir" parameter should represent a directory if not, it will fail to retreive the files.
Ex : If all the PNG files which are located inside a directory which has the path "/path/to/pngfiles/" a FileLoader which can load the PNG files in that directory can be created as :
FileLoader fileLoader = new FileLoader("/path/to/pngfiles","png");
This is the constructor which is recommended to be used in development. The URL "dir" MUST represnt a URL of a file which is in the directory where the other files are needed to be loaded from. If the provided URL is a directory, then this will fail to retrive the files from that directory and instead it will search for the files from the URL's parent directory (since the FileLoader will assume the URL represnts a file, thus will ignore the last part of the path), and if files of the provided extention are found, they will be loaded when called.
Ex : If there's a file called "MyFile.fxml" in a directory and all the "fxml" files from that directory needs to be loaded, then it can be done as :
FileLoader fileLoader = new FileLoader(getClass().getResource("/path/to/file/MyFile.fxml","fxml"));
The constructors with the SceneManager parameter included are same as their constructors without SceneManager paramater on it. These constructors are only used in loading FXML files and managing them through SceneManager objects.
For more details about FileLoader, read the api documentation of FileLoader.
The collect()
method of FileLoader is used to retreive/load the files into memory.It will return a List<File>
object of the loaded PNG files.
Ex : This code will demonstrate creating a FileLoader which is able load PNG files from a directory, and loading those PNG files into memory.
FileLoader fileLoader = new FileLoader(getClass().getResource("/path/to/file/MyFile.png","png"));
List<File> files = fileLoader.collect();
When dealing with multiple views in a JavaFX application and we have to navigate through those different views as shown in A complete implementation what we do is, create FXMLLoader objects and load those scenes when needed. This creates trouble when we have to do so in multiple locations thus has either to load views over and over or keep them in an array or a similar kind of work.
What the SceneManager does is that it can be used to map all those FXML files with their relevent Controllers and you can retreive them whenever needed.
There are two ways of doing so,
First method is to create a SceneManager and map the FXML files and their Controllers with IDs on your own as following :
SceneManager manager = SceneManager();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/resources/Scene1.fxml"));
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("/resources/Scene2.fxml"));
manager.addScene("s1",loader.load(),loader.getController());
manager.addScene("s2",loader2.load(),loader2.getController());
And then those views and their controllers can be retrieved anytime using their mapped ID's.
Node scene1 = manager.getScene("s1"); //gets Scene1.fxml view
manager.getController("s1"); //gets the Controller of Scene1.fxml
Node scene2 = manager.getScene("s2"); //gets Scene2.fxml view
manager.getController("s2"); //gets the Controller of Scene2.fxml
Well, its still not good enough for us lazy-pants does it? Yup. It is not. So, here comes the second method.
SceneManager provides several methods (well, 3 actually) to automate this mapping process, saving you the time of creating all the FXMLLoader objects and keep loader.load()
and loader2.load()
and loader3.load()
and so on.
If all the FXML files are inside the same directory, then all you have to do is,
SceneManager manager = new SceneManager();
manager.automate(getClass().getResource("/path/to/fxmls/Scene1.fxml"));
There. All work done. All the FXML files are now loaded into "manager" and you call them using their FXML file names.
Let's assume the FXML files are called "Scene1.fxml" and "Scene2.fxml" just like the above "method 1" code snippet. In method 1, we mapped the views ourselves giving the IDs we want. But when you automate this process, the IDs are named as for their FXML file's name. So, you can retrieve the views and controllers like this.
Node scene1 = manager.getScene("Scene1"); //gets Scene1.fxml view
manager.getController("Scene1"); //gets the Controller of Scene1.fxml
Node scene2 = manager.getScene("Scene1"); //gets Scene2.fxml view
manager.getController("Scene1"); //gets the Controller of Scene2.fxml
It's as simple as that.
For more details of the automate()
methods and SceneManager, please refer to the SceneManager API Doc
CustomStage is in-built with a static SceneManager object thus it can be used anywhere in your application.
All you have to do is enable the SceneManager object using automate()
in the main class and use it anywhere.
SceneManager manager =CustomStage.getDefaultSceneManager();
manager.automate(getClass().getResource("/path/to/fxmls/Scene1.fxml"));
Will do the trick for you. So you will be able to access the views and controllers anywhere in your application afterwards like this :
SceneManager manager = CustomStage.getDefaultSceneManager();
Node scene1 = manager.getScene("Scene1"); //gets Scene1.fxml view
manager.getController("Scene1"); //gets the Controller of Scene1.fxml
Node scene2 = manager.getScene("Scene1"); //gets Scene2.fxml view
manager.getController("Scene1"); //gets the Controller of Scene2.fxml
Important : If you are using CustomStage's in-built SceneManager, then do not use automate
method with no parameters, it will not work (It will call the FileLoader(String ext)
constructor in SceneManager creationg and the StackTrace is wrong to be used through the CustomStage's in-built SceneManager object).
So always remember to use the manager.automate(URL dir)
method to automate the FXML file loading like done in the above examples.
Assume :
-
"customapp.css", "CustomStageApp.java", "Navigation.fxml", "Scene1.fxml", "Scene2.fxml", "Scene3.fxml", "NavigationController.java", "btnClose.png", "btnMinimize.png", "btnMaximize.png", "btnRestore.png" files are located in the same directory (which is under "src/test").
-
"NavigationController.java" is the controller of "Navigation.fxml" file.
-
"btnS1","btnS2","btnS3" are javafx.scene.control.Button type variables which are included in "Navigation.fxml" and has the texts "Load Scene 1", "Load Scene 2", "Load Scene 3" respectively.
-
All the 3 buttons ("btnS1","btnS2","btnS3") has the style class "nav-button".
Here are the classes (java) and resources (css and fxml)
/*style the buttons in navigationPane*/
.nav-button{
-fx-background-color: #2196F3;
-fx-text-fill: white;
-fx-font-size: 18px;
-fx-font-style: bold;
}
/*change the color of the buttons in navigationPane on hover state*/
.nav-button:hover {
-fx-background-color: #29B6F6;
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #43A047;" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label layoutX="118.0" layoutY="143.0" style="-fx-text-fill: white;" text="This is Scene 1" AnchorPane.rightAnchor="72.39999999999998">
<font>
<Font size="64.0" />
</font>
</Label>
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #00BCD4;" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label layoutX="118.0" layoutY="143.0" style="-fx-text-fill: white;" text="This is Scene 2" AnchorPane.rightAnchor="72.39999999999998">
<font>
<Font size="64.0" />
</font>
</Label>
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #00897B;" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label layoutX="118.0" layoutY="143.0" style="-fx-text-fill: white;" text="This is Scene 3" AnchorPane.rightAnchor="72.39999999999998">
<font>
<Font size="64.0" />
</font>
</Label>
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane style="-fx-background-color: #FBC02D;" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.NavigationController">
<children>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="190.0" spacing="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="btnS1" accessibleRole="PARENT" mnemonicParsing="false" prefHeight="45.0" prefWidth="303.0" styleClass="nav-button" text="Load Scene 1" textFill="WHITE">
<font>
<Font name="System Bold" size="18.0" />
</font></Button>
<Button fx:id="btnS2" accessibleRole="PARENT" mnemonicParsing="false" prefHeight="45.0" prefWidth="303.0" styleClass="nav-button" text="Load Scene 2" textFill="WHITE">
<font>
<Font name="System Bold" size="18.0" />
</font></Button>
<Button fx:id="btnS3" accessibleRole="PARENT" mnemonicParsing="false" prefHeight="45.0" prefWidth="303.0" styleClass="nav-button" text="Load Scene 3" textFill="WHITE">
<font>
<Font name="System Bold" size="18.0" />
</font></Button>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</VBox>
</children>
</AnchorPane>
package test;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import lk.vivoxalabs.customstage.CustomStage;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
/**
* @author oshan
*/
public class NavigationController implements Initializable {
@FXML
Button btnS1,btnS2,btnS3;
private Pane scene1,scene2,scene3;
@Override
public void initialize(URL location, ResourceBundle resources) {
//Load Scene1.fxml,Scene2.fxml and Scene3.fxml files
try {
scene1 = FXMLLoader.load(getClass().getResource("/test/Scene1.fxml"));
scene2 = FXMLLoader.load(getClass().getResource("/test/Scene2.fxml"));
scene3 = FXMLLoader.load(getClass().getResource("/test/Scene3.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
//set Scene1.fxml as the view of the window when "Load Scene 1" is clicked
btnS1.setOnAction(event->{
CustomStage stage = ((CustomStage)btnS1.getScene().getWindow());
stage.changeScene(scene1);
});
//set Scene2.fxml as the view of the window when "Load Scene 2" is clicked
btnS2.setOnAction(event->{
((CustomStage)btnS2.getScene().getWindow()).changeScene(scene2);
});
//set Scene3.fxml as the view of the window when "Load Scene 3" is clicked
btnS3.setOnAction(event->{
((CustomStage)btnS1.getScene().getWindow()).changeScene(scene3);
});
}
}
Those are the additional files needed for the implementation. Now, for the Main class itself, the codings look like following.
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import lk.vivoxalabs.customstage.CustomStage;
import lk.vivoxalabs.customstage.CustomStageBuilder;
import lk.vivoxalabs.customstage.tools.HorizontalPos;
import lk.vivoxalabs.customstage.tools.NavigationType;
import lk.vivoxalabs.customstage.tools.Style;
/**
* @author oshan
*/
public class CustomStageApp extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
AnchorPane navigationPane = FXMLLoader.load(getClass().getResource("/test/Navigation.fxml"));
CustomStage stage = new CustomStageBuilder()
.setWindowTitle("CustomStage App",HorizontalPos.RIGHT,HorizontalPos.CENTER)
.setActionIcons(new Image("/test/btnClose.png"),
new Image("/test/btnMinimize.png"),
new Image("/test/btnMaximize.png"),
new Image("/test/btnRestore.png"))
.setNavigationPane(Style.DYNAMIC,NavigationType.LEFT,navigationPane,60,0,true)
.setTitleColor("black")
.setStyleSheet(getClass().getResource("/test/customapp.css"))
.setIcon("/test/logo.png")
.setButtonHoverColor("FFAB40","FFAB40","d32f2f")
.setDimensions(450,450,1920,1280) //Set min,max values for window resizing
.setWindowColor("FF6D00")
.build();
// Show/hide the navigation pane when the window is clicked
stage.getScene().getRoot().setOnMouseClicked(e->stage.dynamicDrawerEvent(NavigationType.LEFT));
stage.show();
}
}
And this is the output and a full implementation of a CustomStage.
-
- Basic usage
- Transparent Stage
- Changing Scenes
- Importing stylesheet to decorate the CustomStage
- Using custom icons for close,minimize and maximize/restore
- Title-bar customizing and setting application icon
- Using Navigation panes
-
Sample images