Skip to content
Oshan Mendis edited this page Sep 19, 2018 · 28 revisions

CustomStage


Table of contents


  • What is CustomStage?
  • For whom?
  • Benifits of using CustomStage

4) Sample images


Introduction


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.

What is CustomStage ?

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.

For whom?

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?

Then CustomStage is built for you.

Benifits of using CustomStage

  1. Native behaviour of a window

    • Resizability
    • Window draggability
    • Responsiveness
    • Close,minimize,maximize/restore button behaviour
    • Title bar
    • Icon on task bar
  2. Stylable the way you want it to be

    • Style using methods
    • Load a stylesheet
  3. Window aero snaps

  4. 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)
  5. Change the default icons for close,minimize,maximize/restore buttons

  6. Change close,minimize,maximize/restore buttons' hover color

And many more ...


Installing


Starting from version 1.3.1 CustomStage releases are/will be available through JCenter and MavenCentral

Maven

<dependency>
    <groupId>lk.vivoxalabs.customstage</groupId>
    <artifactId>CustomStage</artifactId>
    <version>1.3.2</version>
</dependency>

Gradle

  dependencies {
    compile 'lk.vivoxalabs.customstage:CustomStage:1.3.2'
  }

Download via Jitpack (Will not be possible for releases after v1.3.1)

Gradle

Add jitpack as a repository

repositories {
    maven { url 'https://jitpack.io' }
}

Add dependancy

dependencies {
    compile 'com.github.Oshan96:CustomStage:v1.3.1'
}

Maven

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


Examples


Here are some examples for using CustomStage

Important : CustomStageBuilder class object is used to construct objects of CustomStage.


Basic Usage

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();
    }
}

Result


Transparent Stage

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();
    }
}

Result


Using Custom Icons

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();
    }
}

Result

Title-bar customizing and setting application icon

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();
    }
}

Result

Using Navigation Panes

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)

  1. Static navigation panes - Navigation panes which are always visible in the given location.
  2. 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.

  1. setNavigationPane(type,navigationPane)
  2. setNavigationPane(style,type,navigationPane)
  3. 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.

Static navigation panes

For the static navigation implemetation examples, setNavigationPane(type,navigationPane) method will be used here.

  1. 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();

Left Static Navigation

  1. 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();

Right Static Navigation

  1. 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();

Top Static Navigation

  1. 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();

BottomTop Static Navigation

Dynamic navigation panes

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

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

Using built-in dynamic navigation drawers

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.

dynamicDrawerEvent

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.

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

  1. 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);

Left Dynamic Navigation2

Right dynamic navigation pane can be created and drawer event can be called in the same manner.

  1. 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);

Top Dynamic Navigation

  1. 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);

Multiple Dynamic Navigations




Additional Tools


Apart from the stylable window CustomStage provides. This library consists of some additonal tools which are very helpful in developing JavaFx applications.


FileLoader


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.

FileLoader(String ext)

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.

FileLoader(String dir, String ext)

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");

FileLoader(URL dir, String ext)

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.

Retreive/Load the files using FileLoader object

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();

SceneManager


What is SceneManager?

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.

How to use SceneManager?

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

Using in-built SceneManager of CustomStage

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.


A complete implementation


Assume :

  1. "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").

  2. "NavigationController.java" is the controller of "Navigation.fxml" file.

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

  4. All the 3 buttons ("btnS1","btnS2","btnS3") has the style class "nav-button".

Here are the classes (java) and resources (css and fxml)

customapp.css

/*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;
}

Scene1.fxml

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

Scene2.fxml

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

Scene3.fxml

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

Navigation.fxml

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

NavigationController.java

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.

CustomStageApp.java

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.

Result(https://i.imgur.com/nEpaLKg.gif)