How to create splash screen as a Preloader in JavaFX standalone application? - javafx

I created a Preloader (based on the following tutorial) that should display a splash screen for the main application.
9.3.4 Using a Preloader to Display the Application Initialization Progress
http://docs.oracle.com/javafx/2/deployment/preloaders.htm
public class SplashScreenLoader extends Preloader {
private Stage splashScreen;
#Override
public void start(Stage stage) throws Exception {
splashScreen = stage;
splashScreen.setScene(createScene());
splashScreen.show();
}
public Scene createScene() {
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 200);
return scene;
}
#Override
public void handleApplicationNotification(PreloaderNotification notification) {
if (notification instanceof StateChangeNotification) {
splashScreen.hide();
}
}
}
I'd like to run preloader each time I run the main application in my IDE (IntelliJ IDEA).
I also followed the packaging rules for preloaders in IntelliJ:
https://www.jetbrains.com/idea/help/applications-with-a-preloader-project-organization-and-packaging.html
When I run the main application the preloader doesn't start, so I suppose I'm missing something. I'm new to Preloaders and I don't understand what is the mechanism to connect the main app with the preloader in standalone application.

You can run using LauncherImpl like this . . .
public class Main {
public static void main(String[] args) {
LauncherImpl.launchApplication(MyApplication.class, SplashScreenLoader.class, args);
}
}
And the class MyApplication would be like this . . .
public class MyApplication extends Application {
#Override
public void start(Stage primaryStage) {
....
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

The IDEs aren't great at adding preloaders yet. Take a look at the Manifest in your program's jar file and make sure this line is present:
JavaFX-Preloader-Class: SplashScreenLoader

May be too late, this can also help somebody.
For me, i used JavaFX service and task to create splash screen as a Preloader in JavaFX standalone application. This, because the contexte of my project.
Create the AnchorPane and the progress Pane
#FXML
private AnchorPane anchorPane;
private MaskerPane progressPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void init() throws Exception {
progressPane = new MaskerPane();
progressPane.setText(bundle.getString("root.pleaseWait"));
progressPane.setVisible(false);
AnchorPane.setLeftAnchor(progressPane, 0.0);
AnchorPane.setTopAnchor(progressPane, 0.0);
AnchorPane.setRightAnchor(progressPane, 0.0);
AnchorPane.setBottomAnchor(progressPane, 0.0);
anchorPane.getChildren().add(progressPane);
}
#Override
public void start(Stage initStage) {
//.....
initRoot();
//.....
}
Create the splash screen service as this:
private final Service<Void> splashService = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
//main code, the code who take time
//or
//Thread.sleep(10000);
return null;
}
};
}
};
Start service and show/hide the progressPane on initRoot when loading the main screen:
public void initRoot() {
try {
//....
splashService.restart();
//On succeeded, do this
splashService.setOnRunning(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
//Show mask on succeed
showMask(Boolean.TRUE);
}
});
splashService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
splashService.cancel();
//Hide mask on succeed
showMask(Boolean.FALSE);
}
});
//.....
primaryStage.show();
} catch (IOException ex) {
ex.printStackTrace();
}
}
To show/hide the progress...
showMask(boolean value){
progressPane.setVisible(value);
};

Related

JavaFX log4j in a textarea with a different thread

I'm trying to redirect log4j output to a textarea but this one get filled at the end of the action since I'm using Platform.runLater
Is there any way I can do it with a different thread ?
AppFX.java
public class AppFX extends Application {
public static void main(String[] args) {
ConfHandler.initConf();
launch(args);
}
#Override
public void start(Stage primaryStage)throws IOException {
primaryStage.getIcons().add(new Image("file:src/main/resources/images/frameIcon.png"));
FXMLLoader loader = new FXMLLoader();
String currentPath = System.getProperty("user.dir");
loader.setLocation(new URL("file:\\"+currentPath+"\\src\\main\\resources\\scenes\\TestsFrame.fxml"));
Parent content = loader.load();
primaryStage.setTitle("IGED Tests");
Scene scene = new Scene(content);
primaryStage.setScene(scene);
primaryStage.show();
TextAreaAppender.setTextArea(((EaaSCleanerController)loader.getController()).getLogTextArea());
}}
TextAreaAppender.java
public class TextAreaAppender extends WriterAppender {
private static volatile TextArea textArea = null;
public static void setTextArea(final TextArea textArea) {
TextAreaAppender.textArea = textArea;
}
#Override
public void append(final LoggingEvent loggingEvent) {
final String message = this.layout.format(loggingEvent);
try {
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
if (textArea != null) {
if (textArea.getText().length() == 0) {
textArea.setText(message);
} else {
textArea.selectEnd();
textArea.insertText(textArea.getText().length(), message);
}
}
} catch (final Throwable t) {
System.out.println("Unable to append log to textarea:" + t.getMessage());
}
}
});
} catch (final IllegalStateException e) {
}
}}
log4j.properties
# Append the logs to the GUI
log4j.appender.gui=com.bp.nest.testauto.gui.TextAreaAppender
log4j.appender.gui.layout=org.apache.log4j.PatternLayout
log4j.appender.gui.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p - %m%n
You should put your actions on a separate thread. Then the logging can happen in parallel to your actions. You should never block the GUI-thread.

Binding JavaFX Label to StringProperty

In my JavaFX application I have a label that I want to update with the StringProperty of another class that defines server functionality.
In the server class I have a StringProperty defined as shown below:
public class eol_server {
private StringProperty serverMessagesProperty;
etc..
I have a method to return the StringProperty on request:
public StringProperty serverMessagesProperty() {
if (serverMessagesProperty == null) {
serverMessagesProperty = new SimpleStringProperty();
}
return serverMessagesProperty;
}
In the main gui class, Start() method I build the Scene, then instantiate a new server object. After this I update one of the labels in my Scene graph by binding it to the StringProperty of the server object:
public void start(Stage primaryStage) {
...set up all the gui components
primaryStage.show();
dss = new eol_server();
lbl_dssMessage.textProperty().bind(dss.getServerMessagesProperty());
}
When I run the application, the scene is rendered as I expect, and the lbl_dssMessage text is set to value that is set up in the constructor of eol_server. But, from that point on the binding is not working, although I have actions that would update the StringProperty of the dss object they are not updating the label in the GUI.
Here is the complete file that generates a stripped down version of the scene:
public class eol_gui extends Application {
private static eol_server dss = null;
private static Stage primaryStage;
/**
* Application Main Function
* #param args
*/
public static void main(String[] args) {
launch(args);
}
/**
* #param stage
*/
private void setPrimaryStage(Stage stage) {
eol_gui.primaryStage = stage;
}
/**
* #return Stage for GUI
*/
static public Stage getPrimaryStage() {
return eol_gui.primaryStage;
}
/* (non-Javadoc)
* #see javafx.application.Application#start(javafx.stage.Stage)
*/
#Override
public void start(Stage primaryStage) {
setPrimaryStage(primaryStage);
BorderPane root = new BorderPane();
Label lbl_dssMessage = new Label("initialized");
HBox topMenu = new HBox();
topMenu.getChildren().add(lbl_dssMessage);
eol_supervisor_I config1 = new eol_supervisor_I();
// Build Scene
root.setStyle("-fx-background-color: #FFFFFF;");
root.setTop(topMenu);
primaryStage.setScene(new Scene(root, 300, 50));
primaryStage.show();
dss = new eol_server();
dss.getServerMessagesProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> o,Object oldVal,Object newVal)
{
//Option 1: lbl_dssMessage.setText(newVal.toString());
}
});
//Option 2
lbl_dssMessage.textProperty().bind(dss.serverMessagesProperty);
}
}
As you can see I have tried the bind method and a change listener. It looks like bind was working, as was the listener, in all cases except those that run in the server service threads. These throw the IllegalStateException due to not being on the main JavaFX application thread. How do I safely and correctly exchange messages from the service to the main thread?
The server is defined in the following class, which is intended to run services independent of the main JavaFX thread. But I would like to exchange info between the threads to show status. Ii'm trying to avoid the GUI hanging while the server connections and data exchanges are made.
public class eol_server {
public StringProperty serverMessagesProperty;
public eol_server() {
/* Create Scripting Environment */
serverMessagesProperty().set("Establishing Debug Server Environment");
connect();
}
public boolean connect() {
serverMessagesProperty().set("Creating Server");
ConnectService connectService = new ConnectService();
connectService.start();
return false;
}
public StringProperty serverMessagesProperty() {
if (serverMessagesProperty == null) {
serverMessagesProperty = new SimpleStringProperty();
}
return serverMessagesProperty;
}
public StringProperty getServerMessagesProperty() {
return serverMessagesProperty;
}
private class ConnectService extends Service<Void> {
#Override
protected void succeeded() {
}
#Override
protected void failed() {
}
#Override
protected void cancelled() {
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
serverMessagesProperty().set("Connecting....");
Thread.sleep(5000);
// DEMO: uncomment to provoke "Not on FX application thread"-Exception:
// connectButton.setVisible(false);
serverMessagesProperty().set("Waiting for server feedback");
Thread.sleep(5000);
return null;
}
};
}
}
private class DisconnectService extends Service<Void> {
#Override
protected void succeeded() {
}
#Override
protected void cancelled() {
}
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
updateMessage("Disconnecting....");
serverMessagesProperty().set("Disconnecting....");
Thread.sleep(5000);
updateMessage("Waiting for server feedback.");
serverMessagesProperty().set("Waiting for server feedback.");
Thread.sleep(5000);
return null;
}
};
}
}
}
Thanks in advance
JW
Read the below paragraph from JavaFX Node documentation.
Node objects may be constructed and modified on any thread as long
they are not yet attached to a Scene in a Window that is showing. An
application must attach nodes to such a Scene or modify them on the
JavaFX Application Thread.
The correct and more functional way to solve your problem is
dss.serverMessagesProperty.addListener((observable, oldValue, newValue) -> Platform.runLater(() -> lbl_dssMessage.setText(newValue));
I seem to have a solution, derived from what I read on the runlater() Platform method. Also see discussion here
Multi-Threading error when binding a StringProperty
changing my listener to invoke the message update via runLater seems to break the multi-threading problem. Yes I expect it is not immediate but it will be very very close to immediate and good enough. I appreciate that JavaFX requires you to not mess with the Application thread values / nodes etc etc of the scene graph but it is quite a complicated area of the JavaFX library.
Here is the listener that worked for me
// Get new DSS session active
dss = new eol_server();
dss.serverMessagesProperty.addListener(new ChangeListener<Object>() {
#Override
public void changed (ObservableValue<?> observable, Object oldValue, Object newValue) {
Platform.runLater(() -> lbl_dssMessage.setText(newValue.toString()));
}
});
}
Happy for further discussion as to why this works when the other options did not, also open to any other more elegant suggestions.

Add KeyEvent for calculator numpad in JavaFX using MVC

I created in Eclipse a simple calculator using JavaFx and MVC pattern. I would like to add keylisteners in order to press the buttons of my calculator by simply pressing the buttons in my keyboard. I tried to add #onKeyPress in SceneBuilder and then a method onKeypress (with some coding inside) in my Controller class but nothing happens.Could you please give some general instructions how to implement something like this? Thanks!
Thanks for your comments. I added the following code snippet in App.java:
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
controller.numFromKeyboard(event.getCode().toString());
}
});
And also, I had to add:
Parent root = loader.load();
Controller controller = loader.getController();
// The above line MUST be
// inserted after root is loaded in order the controller of my
// app to be instantiated,
// otherwise we will get a null exception when handler will be
// invoked
App.java
public class App extends Application {
//controller = new Controller();
#Override
public void start(Stage primaryStage) {
try {
// Read file fxml and draw interface.
FXMLLoader loader = new FXMLLoader(getClass()
.getResource("/application/View.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("/application/application.css").toExternalForm());
Image icon = new Image(getClass().getResourceAsStream("/application/Assets/App.png"));
primaryStage.getIcons().add(icon);
primaryStage.setTitle("JavaFX Calculator by Dimitris Baltas");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
controller.numFromKeyboard(event.getCode().toString());
}
});
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}

JAVAFX Make dynamic textArea size

I'm making chat application using JAVAFX. The messages displayed in textArea, but the textArea always in the same size. How can I make the textArea to fit exactly to the amount of text? TNX
The following code does exactly what You wants
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
AnchorPane root = new AnchorPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
TextArea ta=new TextArea();
ta.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// your algorithm to change height
ta.setPrefHeight(ta.getPrefHeight()+10);
}
});
root.getChildren().add(ta);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}

JavaFX preloader not updating progress

I'm having trouble with the JavaFX Preloader. During the start phase the application will have to connect to a DB and read many so I thought it would be nice to display a splash screen during this time. The problem is the ProgressBar automaticly goes to 100% and I don't understand why.
Application class. Thread sleep will be replaced by real code later (DB connection etc)
public void init() throws InterruptedException
{
notifyPreloader(new Preloader.ProgressNotification(0.0));
Thread.sleep(5000);
notifyPreloader(new Preloader.ProgressNotification(0.1));
Thread.sleep(5000);
notifyPreloader(new Preloader.ProgressNotification(0.2));
}
Preloader
public class PreloaderDemo extends Preloader {
ProgressBar bar;
Stage stage;
private Scene createPreloaderScene() {
bar = new ProgressBar();
bar.getProgress();
BorderPane p = new BorderPane();
p.setCenter(bar);
return new Scene(p, 300, 150);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleStateChangeNotification(StateChangeNotification scn) {
if (scn.getType() == StateChangeNotification.Type.BEFORE_START) {
stage.hide();
}
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
bar.setProgress(pn.getProgress());
System.out.println("Progress " + bar.getProgress());
}
For some reason I get the following output:
Progress 0.0
Progress 1.0
I had same problem and I found solution after two hours of searching and 5 minutes of carefully reading of JavaDoc.:)
Notifications send by notifyPreloader() method can be handled only by Preloader.handleApplicationNotification() method and it doesn't matter which type of notification are you sending.
So change you code like this:
public class PreloaderDemo extends Preloader {
.... everything like it was and add this ...
#Override
public void handleApplicationNotification(PreloaderNotification arg0) {
if (arg0 instanceof ProgressNotification) {
ProgressNotification pn= (ProgressNotification) arg0;
bar.setProgress(pn.getProgress());
System.out.println("Progress " + bar.getProgress());
}
}
}

Resources