Reputation: 1
I am developing an applications that can run on 2 different interfaces: one is JavaFX, the other is CLI. During the usage of the applications, i have to run a new stage that implements some sort of fake payment loading a new stage with a new FXML... in the JavaFX run everything runs just fine, but in the CLI i get this error: "This operation is permitted on the event thread only; currentThread = main" which i think is caused because my main doesn't extend "Applications" and cannot load a stage. This is the CLI main (pretty simple).
public static void main(String[] args){
LoginControllerG2 loginControllerG2 = new LoginControllerG2();
loginControllerG2.getRole();
}
I need to load the stage with my CLI interface.
Upvotes: 0
Views: 236
Reputation: 209330
If your "CLI" shows a JavaFX window, then it is a JavaFX application. Subclass Application
in the usual way.
Your application does not need to show the primary stage if it's not ready to during the start method. You can launch your CLI and let the user interact with it.
Note that whatever approach you use here, you have to manage two threads: the JavaFX Application Thread and the thread on which the CLI runs. Since the CLI is going to block for user input, it must not run on the FX Application Thread.
If you launch the CLI from Application.start()
, since Application.start()
is executed on the FX Application Thread, you need to create a new thread for it.
Use all the usual multithreading precautions for sharing data between the two threads.
Here is a quick example. Here's a very basic CLI class with a method that runs a simple REPL. There are only two commands: "login"
shows a login screen using JavaFX and retrieves some data from it. "exit"
exits the application.
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.FutureTask;
public class CommandLineInterpreter {
public void runCli() {
try (Scanner scanner = new Scanner(System.in)) {
boolean done = false;
while (! done) {
System.out.println("Enter login to log in, or exit to quit:");
String input = scanner.nextLine();
if ("login".equalsIgnoreCase(input)) {
String user = getLoginData();
System.out.println("Welcome "+user);
} else if (input.equalsIgnoreCase("exit")) {
Platform.exit();
done = true ;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private String getLoginData() throws Exception{
FutureTask<String> loginWithUITask = new FutureTask<>(this::getUserFromUI);
Platform.runLater(loginWithUITask);
return loginWithUITask.get();
}
private String getUserFromUI() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
Scene scene = new Scene(root);
Stage stage = new Stage();
stage.setScene(scene);
stage.setOnShown(e -> stage.toFront());
stage.showAndWait();
return controller.getUser();
}
}
Here's a basic application class that starts the CLI above in a background thread. Note that we have to call Platform.setImplicitExit(false)
to make sure the FX platform doesn't close down when the last window is closed.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
Platform.setImplicitExit(false);
CommandLineInterpreter cli = new CommandLineInterpreter();
Thread cliThread = new Thread(cli::runCli);
cliThread.start();
}
@Override
public void stop() {
// Cleanup code...
}
public static void main(String[] args) {
launch();
}
}
And for completeness the FXML and controller class, which are nothing special:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<GridPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jamesd.examples.cli.LoginController"
hgap="5" vgap="5">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER"/>
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS"/>
</columnConstraints>
<padding><Insets topRightBottomLeft="5"/></padding>
<Label text="User Name:" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<TextField fx:id="userField" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<Label text="Password" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<PasswordField GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Button text="OK" onAction="#login"
GridPane.columnIndex="0" GridPane.rowIndex="2"
GridPane.columnSpan="2" GridPane.halignment="CENTER"/>
</GridPane>
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class LoginController {
@FXML
private TextField userField ;
@FXML
private void login() {
userField.getScene().getWindow().hide();
}
public String getUser() {
return userField.getText();
}
}
Other approaches are possible, e.g. you might use Platform.startup()
in a non-JavaFX thread to "manually" start the FX platform, bypassing the usual application lifecycle structure. For example, you can replace the HelloApplication
class above with this implementation:
import javafx.application.Platform;
public class HelloApplication {
public static void main(String[] args) {
Platform.startup(() -> Platform.setImplicitExit(false));
CommandLineInterpreter cli = new CommandLineInterpreter();
cli.runCli();
}
}
Note that with this approach you don't have a stop()
method to perform any cleanup code, as you do with the previous approach.
Upvotes: 3