Javafx run another class using hyperlink [duplicate] - javafx

I've been smashing my head with JavaFx...
This works for when there's no instances of an application running:
public class Runner {
public static void main(String[] args) {
anotherApp app = new anotherApp();
new Thread(app).start();
}
}
public class anotherApp extends Application implements Runnable {
#Override
public void start(Stage stage) {
}
#Override
public void run(){
launch();
}
}
But if I do new Thread(app).start() within another application I get an exception stating that I can't do two launches.
Also my method is called by an observer on the other application like this:
#Override
public void update(Observable o, Object arg) {
// new anotherApp().start(new Stage());
/* Not on FX application thread; exception */
// new Thread(new anotherApp()).start();
/* java.lang.IllegalStateException: Application launch must not be called more than once */
}
It's within a JavaFX class such as this:
public class Runner extends Applications implements Observer {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage){
//...code...//
}
//...methods..//
//...methods..//
#Override
public void update(Observable o, Object arg) {
//the code posted above//
}
}
I tried using ObjectProperties with listeners but it didn't work. I need to get this new stage running from within the update method from java.util.observer in some way.
Any suggestions are welcomed. Thanks.

Application is not just a window -- it's a Process. Thus only one Application#launch() is allowed per VM.
If you want to have a new window -- create a Stage.
If you really want to reuse anotherApp class, just wrap it in Platform.runLater()
#Override
public void update(Observable o, Object arg) {
Platform.runLater(new Runnable() {
public void run() {
new anotherApp().start(new Stage());
}
});
}

I did a constructor of another JFX class in Main class AnotherClass ac = new AnotherClass(); and then called the method ac.start(new Stage);. it worked me fine. U can put it either in main() or in another method. It does probably the same thing the launch(args) method does

Wanted to provide a second answer because of one caveat of using Application.start(Stage stage).
The start method is called after the init method has returned
If your JavaFX application has Override Application.init() then that code is never executed. Neither is any code you have in the second application main method.
Another way to start a second JavaFX application is by using the ProcessBuilder API to start a new process.
final String javaHome = System.getProperty("java.home");
final String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
final String classpath = System.getProperty("java.class.path");
final Class<TestApplication2> klass = TestApplication2.class;
final String className = klass.getCanonicalName();
final ProcessBuilder builder = new ProcessBuilder(javaBin, "-cp", classpath, className);
final Button button = new Button("Launch");
button.setOnAction(event -> {
try {
Process process = builder.start();
} catch (IOException e) {
e.printStackTrace();
}
});

Related

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.

Update UI in method called from ActionEvent thread using JavaFX

I want to change the stage when a user logs in the application.
I have created a thread in the Action and inside it I use Platform.runLater to update the stage and show the new one. This is done in the called method.
So I have the following code:
Logincontroller
private Stage primaryStage
#FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
public void run() {
....
if (user exists and password ok){
loadMainwindow();
}else{
show alert
}
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}
private void loadMainWindow() throws IOException {
dummyStage = (Stage) (btnLogin.getScene()).getWindow();
//I get the root borderpain from the Main class
BorderPane root = Main.getRoot();
//I load the anchorpanes i will use in the new stage
AnchorPane menuPane =
FXMLLoader.load(getClass().getResource("/views/Menu.fxml"));
AnchorPane centerPane =
FXMLLoader.load(getClass().getResource("/views/Home.fxml"));
//I set the anchorpanes to the root
root.setLeft(menuPane);
root.setCenter(centerPane);
Platform.runLater(new Runnable() {
public void run() {
primaryStage.show();
}
});
}
And I´m having the following error:
Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at javafx.graphics/javafx.scene.layout.BorderPane$BorderPositionProperty.invalidated(BorderPane.java:692)
at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
at javafx.graphics/javafx.scene.layout.BorderPane.setLeft(BorderPane.java:325)
at com.sener.dbgui.controller.LoginController.loadMainWindow(LoginController.java:90)
at com.sener.dbgui.controller.LoginController.access$4(LoginController.java:81)
at com.sener.dbgui.controller.LoginController$1.run(LoginController.java:63)
at java.base/java.lang.Thread.run(Thread.java:844)
Line 81 is the "root.setLeft(menuPane)" line.
So I guess the problem is that when modifying the root borderpane the JAVAFX thread must be running. This is, I must include the "root.set..." statements in the Platform.runLater method.
Nonetheless, this would imply in setting multiple variables for root, menuPane and centerPane to private in the controller class so that Platform.runLater process could access them and all the FXMLLoader, getwindow() and getRoot() methods could be decoupled from Platform.runLater.
So, is it better to create set this variables to private or just call the method inside the Platform.runLater?
OPTION 1. CALL METHOD INSIDE Platform.runLater
#FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
public void run() {
....
if (user exists and password ok){
Platform.runLater(new Runnable() {
public void run() {
loadMainwindow();
}
});
}else{
show alert
}
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}
If decoupling FXMLLoader, getWindow() and getRoot methods from Platform.runLater the code of the method would look like this (I would first create private variables for AnchorPanes "menuPane" and "centerPane", BorderPane "root" just like with "primaryStage" variable):
OPTION 2. CALL METHOD AND DECOUPLE FMLXLOADERS, GETROOT() AND GETWINDOW() METHODS FROM Platform.runLater()
private AnchorPane menuPane, centerPane;
private Stage dummyStage;
private BorderPane root;
#FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
public void run() {
....
if (user exists and password ok){
loadMainwindow();
}else{
show alert
}
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}
private void loadMainWindow() throws IOException {
root = Main.getRoot();
primaryStage = (Stage) (btnLogin.getScene()).getWindow();
menuPane = FXMLLoader.load(getClass().getResource("/views/Menu.fxml"));
centerPane = XMLLoader.load(getClass().getResource("/views/Home.fxml"));
Platform.runLater(new Runnable() {
public void run() {
root.setLeft(menuPane);
root.setCenter(centerPane);
primaryStage.toFront();
primaryStage.show();
}
});
}
I would like to know which option is the correct one. Or maybe these are wrong and there´s another solution to this.

JavaFX window setTitle

I have a main class which is the following:
public class Window extends Application {
#Override
public void start(Stage foablak) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Foablak.fxml"));
Scene scene = new Scene(root);
foablak.setScene(scene);
foablak.setWidth(900);
foablak.setHeight(700);
foablak.setResizable(false);
foablak.setTitle("Window");
foablak.show();
}
public static void main(String[] args) {
launch(args);
}
}
How can I update the Title of the from another .java class without closing the window and open a new one?
Exposing properties using static, just for the sake of exposing, may be considered as a bad design. You have different ways to achieve the same, for example, expose a method from the Window class which sets the stage title.
public class Window extends Application {
private Stage stage;
#Override
public void start(Stage foablak) throws Exception {
stage = foablak;
Parent root = FXMLLoader.load(getClass().getResource("Foablak.fxml"));
Scene scene = new Scene(root);
foablak.setScene(scene);
foablak.setWidth(900);
foablak.setHeight(700);
foablak.setResizable(false);
foablak.setTitle("Window");
foablak.show();
}
public static void main(String[] args) {
launch(args);
}
public void setStageTitle(String newTitle) {
stage.setTitle(newTitle);
}
}
Yes, you can. Inside of your Application.start() method, save a reference to your primary Stage that you can access elsewhere, and then call Stage.setTitle().
class MyApplication extends Application {
public static Stage primaryStage;
#Override
public void start(Stage primaryStage) {
MyApplication.primaryStage = primaryStage;
// ...
}
}
MyApplication.primaryStage.setTitle("New Title");
As an aside, I would avoid calling your class Window, as that is the name of one of the JavaFX classes.
The following may not be the solution you were looking for, but it might be useful for some of the developers:
Scenario: There is only 1 JavaFX app; The app needs to be run
multiple times; You want to differentiate who is running the app
Attention: Pay attention of the running order of the Main and
Controller class
Solution: i. In the Controller Class, declare a private static variable, e.g., private static String strWho; ii. Expose strWho by providing a getter method. e.g.: public static String getWho(){
return strWho;
}; iii. Implement the initialize method for the Controller, and based on your need, assign a distinct value each time you run the JavaFX app. eg., #FXML
public void initialize() {
strWho = "you need to have logic here, to have a distinct value each time you run the app";
}
In the Main start method, right before you call the stage.show, set the title. eg:
primaryStage.setTitle(Controller.getWho()));
primaryStage.show();
One way to implement the logic for distinct value of the strWho each time you run the app: You can have a TextInputDialog in the Controller's initialize method, to accept user input, by asking for a name etc.

How to update text box in JavaFX Application after 5 seconds of running?

I have a single controller class "FXController.java" and my FXApplication.java that extends "Application" and contains the launch code. In a separate class "TestFX.java" I call the start method in the "FXApplication.java" that starts the gui. I want to be able to access its controller so that I can change the text within a textfield of the controller. In my FXApplication.java, within the "launch" method I create a variable for the FXLoader and use the "getController" method and set it to a public variable: public FXController theController.
Within the TestFX.java, after I call the "start" method in the main method that launches FXApplication.java in a new runnable, I try to access the controller to change the contents of a single textfield, I get an exception that says controller is null. What is the proper way for me to change the contents of the textfield? I feel that the threading is causing problems.
What I am trying to do in my main method is:
Launch the JavaFX Application/GUI
5 seconds later (sleep), change the text of the text field in FXController.java to "Hello World".
Note that the fxml file loaded/used by FXApplication.java is pointed correctly to the FXController.java. Am wondering if there is some way to access the controller despite having spawned a new runnable for the FX application.
FXApplication.java
public class FXApplication extends Application {
public FXController theController;
public void start() {
Application.launch(FXApplication.class, args);
}
#Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmll = new FXMLLoader(getClass().getResource("fxml_example.fxml"));
Parent root = fxmll.load();
theController = fxmll.getController();
stage.setTitle("FXML Welcome");
stage.setScene(new Scene(root, 300, 275));
stage.show();
}
}
My TestFX.java
public class TestFX {
public static FXApplication fxApp = new FXApplication();
public ExecutorService execs;
public Future<?> fut;
TestFX(ExecutorService execs) {
this.execs = execs;
}
public void start() {
fut = execs.submit(new Runnable() {
#Override
public void run() {
fxApp.start();
}
});
}
public static void main(String[] args) {
ExecutorService execs = Executors.newCachedThreadPool();
TestFX testFx = new TestFX(execs);
testFx.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//fxApp.theController.setTextBoxText("Hello Word");
Platform.runLater(() -> fxApp.theController.setTextBoxText("Hello Word"));
}
Stuff that you want to do like this, you should use the Task class. This does all the heavy lifting for you and all you have to do is set up the code you want to run the FXAT when the task completes. Here's an example, I've left out the controller stuff, because it just clutters up the concepts:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
private static Label label;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
label = new Label();
label.setText("Waiting...");
StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
label.setText("Hello World");
}
});
new Thread(sleeper).start();
}
}
The "sleeper" Task doesn't do anything except sleep, but it's going to sleep on a new thread so the FXAT can keep on responding to screen activity. Then when the sleep finishes, the event handler for the succeed will run on the thread that instantiated the Task, in this case the FXAT.
Your code has two problems.
First, you are calling the static FXMLLoader.load(URL) method, instead of calling load on your FXMLLoader instance. Consequently, the FXMLLoader instance never gets to initialize its controller. You need
FXMLLoader fxmll = new FXMLLoader(getClass().getResource("fxml_example.fxml"));
Parent root = fxmll.load();
The second issue is that you are then changing the text of the text box from a background thread, instead of from the FX Application Thread. (Unless you're handling this in the controller class: you don't show the code for that.) You need
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() -> fxApp.theController.setTextBoxText("Hello Word"));
You can also do this with a PauseTransition:
PauseTransition pause = new PauseTransition(Duration.seconds(5));
pause.setOnFinished(event -> fxApp.theController.setTextBoxText("Hello Word"));
pause.play();

JavaFX - Using Progressbar on dynamically loaded Gridpane

I'm using a GridPane, where I'm loading my data from a DB to the Pane, creating rows and columns dynamically. It works fine, but sometimes, if there is a lot of stuff (about 30 rows and 30 columns, and every cell has a EventListener) to load, it takes more than a second to see the loaded stuff.
So, I thought, that it will be great to add a ProgressBar or a ProgressIndicator. I tried that, but I realized that it does not depend on the loops, which are filling and creating the dynamic GridPane, but on the fact that the "loading and applying" takes a lot of time. My ProgressIndicator jumps from 0 to 100 and that's not what I want.
Do you have some ideas how to solve that problem? I read some articles about Preloaders, and I'm looking for a similar functionality, but I can't use Preloaders for that problem.
Best regards,
Edit: Now I got a solution, which works, but I don't think that someone else would write such code :S.
I created a class called LoaderDienstplan:
public class LoaderDienstplan extends Task {
//Some member variables for starting call() method
#Override
protected Object[] call() throws Exception{
// calling DB and service
updateProgress(1,1);
return; // Returning an ObjectArray with Lists,Data and a reference to my Controllerclass
}
#Override
public void run(){
final Object[] b = call();
Platform.runLater(new Runnable(){
#Override
public void run(){
//Updating GridPane
}
});
}
}
public class LoaderDienstplan extends Task {
MyInterface listener;
//Some member variables for starting call() method
#Override
protected Object[] call() throws Exception{
// calling DB and service
updateProgress(1,1);
return; // Returning an ObjectArray with Lists,Data and a reference to my Controllerclass
}
public void setListener(MyInterface listener) {
this.listener = listener;
}
#Override
public void run() {
final Object[] b = call();
Platform.runLater(new Runnable(){
#Override
public void run() {
//Updating GridPane
listener.updateGUI(something);
}
});
}
}
And the interface:
public interface MyInterface {
void updateGUI(Something something);
void loadingFinished();
}
And from your Controller class, must implements MyInterface:
LoaderDienstplan load = new LoaderDienstplan(...);
load.setListener(this);
Make this class:
public class Loader implements Runnable {
private final MyController listener;
private final List<File> stuff;
public DoubleProperty progressProperty;
public Loader(MyController listener, List<File> stuffToLoad) {
this.stuff = stuffToLoad;
progressProperty = new SimpleDoubleProperty();
this.listener = listener;
}
private void updateProgress(double value, double max) {
progressProperty.set(value / max);
}
public DoubleProperty progressProperty() {
return progressProperty;
}
#Override
public void run() {
Platform.runLater(new Runnable() {
public void run() {
//load the stuffs
}
});
//each time you load another stuff, do:
updateProgress(index, totalAmountOfStuffs);
//when finished, do:
listener.finished();
}
}
Put a veil and an indicator around the GridPane, like this (i made the veil (Region) and indicator (ProgressIndicator) from the JavaFX SceneBuilder):
//lets say your progress indicator is called progressIndicator and the shaded `Region` is called veil
progressIndicator.setMaxSize(150, 150);
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
And run the Loader class from another thread like this:
private void startLoading() {
progressIndicator.setVisible(true);
veil.setVisible(true);
Loader loader = new Loader(this, stuffToLoad);
progressIndicator.progressProperty().bind(loader.progressProperty());
new Thread(loader).start();
}
#Override
public void finished() {
progressIndicator.setVisible(false);
veil.setVisible(false);
}
What it looks like for me:

Resources