How to create your first start class.
JRebirth Application Framework offers a custom class that extends the basic javafx.Application class, the aim is to automatically start the JRebirth underlying Application Framework without doing complex stuff.
Short UML Diagram:
To trigger the start-up of your JavaFX application you must add a static void main method in order to call one of the static protected method provided :
106 116 127 136 146 | protected static void preloadAndLaunch( final String... args) { protected static void preloadAndLaunch( final Class<? extends Preloader> preloaderClass, final String... args) { protected static void preloadAndLaunch( final Class<? extends Application> appClass, final Class<? extends Preloader> preloaderClass, final String... args) { protected static void launchNow( final String... args) { protected static void launchNow( final Class<? extends Application> appClass, final String... args) { |
In example, SampleApplication will be launched with default JRebirth preloader (Application and Preloader classes are omitted) like below:
30 31 32 | public static void main( final String... args) { preloadAndLaunch(args); } |
If you want to use the JRebirthPreloader, you must include the JRebirth preloader artifact to your pom.xml file
1 2 3 4 5 | < dependency > < groupId >org.jrebirth.af</ groupId > < artifactId >preloader</ artifactId > < version >8.0.3</ version > </ dependency > |
You can create your own Preloader class, JRebirth send only ProgressNotification with two kind of values:
Hereafter you have the default JRebirthPreloader implementation.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | public class JRebirthPreloader extends Preloader { /** The Progress Bar. */ private ProgressBar progressBar; /** The preloader Stage. */ private Stage preloaderStage; /** The text that will display message received. */ private Text messageText; /** Flag that indicate if the application is initialized. */ private boolean jrebirthInitialized = false ; /** * {@inheritDoc} */ @Override public void init() throws Exception { super .init(); // Nothing to do for the preloader } /** * {@inheritDoc} */ @Override public void start( final Stage stage) throws Exception { // Store the preloader stage to reuse it later this .preloaderStage = stage; // Configure the stage stage.centerOnScreen(); stage.initStyle(StageStyle.TRANSPARENT); stage.setScene(createPreloaderScene()); // Let's start the show stage.show(); } /** * Creates the preloader scene. * * @return the scene */ private Scene createPreloaderScene() { final StackPane p = new StackPane(); final ImageView logo = new ImageView( new Image( "JRebirth_Title.png" )); p.getChildren().add(logo); StackPane.setAlignment(logo, Pos.CENTER); this .progressBar = new ProgressBar( 0.0 ); this .progressBar.setPrefSize( 460 , 20 ); p.getChildren().add( this .progressBar); StackPane.setAlignment( this .progressBar, Pos.BOTTOM_CENTER); StackPane.setMargin( this .progressBar, new Insets( 30 )); this .messageText = new Text( "Loading" ); p.getChildren().add( this .messageText); StackPane.setAlignment( this .messageText, Pos.BOTTOM_CENTER); StackPane.setMargin( this .messageText, new Insets( 10 )); return new Scene(p, 600 , 200 , Color.TRANSPARENT); } /** * {@inheritDoc} */ @Override public void stop() throws Exception { super .stop(); } /** * {@inheritDoc} */ @Override public void handleProgressNotification( final ProgressNotification pn) { if ( this .jrebirthInitialized && pn.getProgress() >= 0.0 && pn.getProgress() <= 1.0 ) { this .progressBar.setProgress(pn.getProgress()); } else { this .messageText.setText(getMessageFromCode(( int ) pn.getProgress())); } } /** * Gets the message from code. * * @param messageCode the message code * * @return the message from code */ private String getMessageFromCode( final int messageCode) { String res = "" ; switch (messageCode) { case 100 : res = "Initializing" ; break ; case 200 : res = "" ; // Provisioned for custom pre-init task break ; case 300 : res = "" ; // Provisioned for custom pre-init task break ; case 400 : res = "Loading Messages Properties" ; break ; case 500 : res = "Loading Parameters Properties" ; break ; case 600 : res = "Preparing Core Engine" ; break ; case 700 : res = "Preloading Resources" ; break ; case 800 : res = "Preloading Modules" ; break ; case 900 : res = "" ; // Provisioned for custom post-init task break ; case 1000 : res = "Starting" ; break ; default : } return res; } /** * {@inheritDoc} */ @Override public void handleApplicationNotification( final PreloaderNotification n) { if (n instanceof MessageNotification) { this .messageText.setText(((MessageNotification) n).getMessage()); } else if (n instanceof ProgressNotification) { handleProgressNotification((ProgressNotification) n); } } /** * {@inheritDoc} */ @Override public boolean handleErrorNotification( final ErrorNotification arg0) { return super .handleErrorNotification(arg0); } /** * {@inheritDoc} */ @Override public void handleStateChangeNotification( final StateChangeNotification event) { switch (event.getType()) { case BEFORE_LOAD: break ; case BEFORE_INIT: this .jrebirthInitialized = true ; break ; case BEFORE_START: beforeStart(); break ; default : } } /** * Perform actions before the application start. * * @throws InterruptedException */ private void beforeStart() { final Stage stage = this .preloaderStage; ScaleTransitionBuilder.create() .fromX( 1.0 ) .toX( 0.0 ) .duration(Duration.millis( 400 )) .node(stage.getScene().getRoot()) .onFinished( new EventHandler<ActionEvent>() { @Override public void handle( final ActionEvent arg0) { stage.hide(); } }) .build().play(); } } |
To define your own Application class you have 2 options:
The AbstractApplication class will do all extra stuff required to launch JRebirth engine.
You don't have to bother about it.
This class skeleton provides some hooks to allow customization of the
application start up.
Please note that one method is mandatory ! You must define a first Model Class to load the first Model that will initialize the first Node attached to the RootNode (automagically created) )of your Scene.
If you have used the Maven archetype org.jrebirth.archetype you obtained this source code otherwise that you can copy-paste:
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | public final class SampleApplication extends DefaultApplication<StackPane> { /** * Application launcher. * * @param args the command line arguments */ public static void main( final String... args) { preloadAndLaunch(args); } /** * {@inheritDoc} */ @Override public Class<? extends Model> getFirstModelClass() { return SampleModel. class ; } /** * {@inheritDoc} */ @Override protected void customizeStage( final Stage stage) { stage.setFullScreen( false ); } /** * {@inheritDoc} */ @Override protected void customizeScene( final Scene scene) { addCSS(scene, SampleStyles.MAIN); } /** * {@inheritDoc} */ @Override protected List<? extends ResourceItem<?, ?, ?>> getResourceToPreload() { return Arrays.asList( new FontItem[] { SampleFonts.SPLASH, }); } } |
The SampleModel.class is shown and explained into the Ui page.
AbstractApplication & DefaultApplication classes are using a generic type that represents the first JavaFX node hold by the scene. This node will be instantiated automatically by the framework and could be accessed by calling the protected method getRootNode(). You must define it in the class definition as generic type, this type must extend Pane class.
The method Class<? extends Model> getFirstModelClass() is mandatory to define which UI model will be attached to the root node of the scene.
This first model will be created into the JRebirth Thread Pool (JTP), then it will be attached to the root node into the JavaFX Application Thread (JAT).
The method String getApplicationTitle() is simply used to define the name of the application displayed by the stage (OS window) and used by OS task bar.
By default it will retrieve values from properties file (default is jrebirth.properties):
The first stage object is provided by the JavaFX launcher, the method void customizeStage(final Stage stage) allows doing some stage customizations.
The scene object automatically attached to the default stage stage is built by the protected method Scene buildScene() that could be overridden as needed. By defaut it creates a default scene with these attributes :
Theses properties are customizable with a properties file, this is explained below into the Configuration section.
Another method let you customize the scene : void customizeSscene(final Scene scene) . For example you can listen some key binding to perform a global action. The Presentation application uses it to listen <Ctrl> + <+> and <Ctrl> + <-> key combo to zoom in/out the whole scene.
This method is also useful to attach a stylesheet to the scene like this : scene.getStylesheets().add(loadCSS("style/sample.css"));
JavaFX applications are able to use fonts through programmatic
declarations or with CSS declaration (in .css files. or inline).
If font used by CSS are not provided by the platform running the
application, it must be provided and loaded by the application.
JRebirth provides a simple way to embed and declare font: this mechanism
is explained in the custom topic: Managing Fonts.
The method List<FontEnum> getFontToPreload() is used to preload fonts to allow them to be used by CSS declaration. They are loaded at boot in the same time than stylesheets.
The JRebirth Application class allow running Waves before and after the creation of the first model class.
A Wave is a JRebirth Event that could be process by any JRebirth components, they are carrying throught JRebirth classes and threads.
You can add your own wave with the two following methods :
The waves returnes will be processed sequentially althought they could be processed by different threads.
In this method you are allowed to call visible methods from the javafx.application.Application class, in example the getParameter() will give you the arguments passed to command line
Don't forget that you can chain your waves if you need to do more than one thing.
JRebirth Analyzer example :
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | @Override public List<Wave> getPostBootWaveList() { final List<Wave> waveList = new ArrayList<>(); // Get Java command line parameters final Parameters p = getParameters(); if (p.getRaw().size() >= 1 ) { // The first parameter must contains the log file to parse final String logFileName = p.getRaw().get( 0 ); final File logFile = new File(logFileName); if (logFile.exists()) { // Call the service that will load and parse the log file waveList.add( Builders.wave() .waveGroup(WaveGroup.RETURN_DATA) .waveType(LoadEdtFileService.DO_LOAD_EVENTS) .componentClass(LoadEdtFileService. class ) .addDatas(Builders.waveData(EditorWaves.EVENTS_FILE, logFile)) ); // Start the animation to show all components creation waveList.add(Builders.wave().waveType(EditorWaves.DO_PLAY)); } } return waveList; } |
The AbstractApplication class is using two defaults hotkey:
638 | protected KeyCode getFullScreenKeyCode() { |
649 | protected KeyCode getIconifiedKeyCode() { |
These methods could be overridden if you want to change them, you can avoid these shortcut by returning null .
JRebirth creates its own uncaught exception handlers in order to log
exceptions that were not caught by application code.
It's possible to customize them by overriding methods listed hereafter:
667 | protected UncaughtExceptionHandler getJatUncaughtExceptionHandler() { |
676 | protected UncaughtExceptionHandler getJitUncaughtExceptionHandler() { |
658 | protected UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { |
JavaFX applications haves 2 main phases while starting up:
The Initialization phase is composed by:
Customizable steps are handled by 2 methods to override:
209 210 211 212 | /** * Perform custom task before application initialization phase. */ protected abstract void preInit(); |
You can use notifyPreloader(new ProgressNotification(PROGRESS)) method where PROGRESS value
is between 0.11 and 0.29 in order to update finely the progression.
You can also display custom message (understandable by your preloader) by calling
notifyPreloader(new ProgressNotification(MESSAGE_ID)) where MESSAGE_ID is 200 or 300.
214 215 216 217 | /** * Perform custom task after application initialization phase and before starting phase. */ protected abstract void postInit(); |
You can use notifyPreloader(new ProgressNotification(PROGRESS)) method where PROGRESS value
is between 0.71 and 0.89 in order to update finely the progression.
You can also display custom message (understandable by your preloader) by calling
notifyPreloader(new ProgressNotification(MESSAGE_ID)) where MESSAGE_ID is 800 or 900.
JRebirth provides a configuration engine that allow to parse configuration files and inject values into application.
Your application class can use the dedicated @Configuration annotation. The AbstractApplication class use a default one:
79 | @Configuration ( ".*jrebirth" ) |
Hereafter you have a full annotation usage:
14 | @Configuration (value = ".*-jrebirth" , extension = "properties" , schedule = 60 ) |
This annotation has 3 properties:
Define the wildcard used to find configuration files.
The format is the same as Regex Pattern (ie: .*-jrebirth => for abc-jrebirth.EXTENSION files, abc
matches the .* regex part)
The default value is empty, no search will be
done
JRebirth provides a Internationalization engine that allow to localize internal resources and also your resources. It parses properties files and inject values into application.
Your application class can use the dedicated @Localized annotation. The AbstractApplication class use a default one:
80 | @Localized ( ".*_rb" ) |
This annotation has 2 properties: