Lkmuraz
Lkmuraz

Reputation: 141

Running java Application with Multiple UI Threads

I'm trying to coding a very simple Client-Server Email project in java. I've already code the communication between client and server using socket and now I'm tryng to code some test which includes also a very simple UI. My idea is to create many threads as many clients I have and I want that every sigle thread starts opening a simple UI window created with Java FX but I have some problems.

This is the main class:

import java.io.*;

public class ClientController{
    public static void main(String args[]) throws IOException {
        ParallelClient c1=new ParallelClient("[email protected]");
        ParallelClient c2=new ParallelClient("[email protected]");
        c1.start();
        c2.start();
    }
}

This is the ParallelClient class:

import ...

public class ParallelClient extends Thread{
    private String user;

    public ParallelClient(String user){
        this.user=user;
    }

    public void run(){
        ClientApp app=new ClientApp();
        try {
            app.start(new Stage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        ...
    }
    ...
}

And this is the ClientApp class which set the new window:

import ...

public class ClientApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("ui/client-management.fxml"));
            stage.setTitle("ClientMail");
            stage.setScene(new Scene(root, 1080, 720));
            stage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

When I try to run the code I get the followung problem and I can't understand how to fix it:

Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
    at javafx.stage.Window.<init>(Window.java:1439)
    at javafx.stage.Stage.<init>(Stage.java:252)
    at javafx.stage.Stage.<init>(Stage.java:240)
    at model.ParallelClient.run(ParallelClient.java:25)
java.lang.ExceptionInInitializerError
    at javafx.stage.Window.<init>(Window.java:1439)
    at javafx.stage.Stage.<init>(Stage.java:252)
    at javafx.stage.Stage.<init>(Stage.java:240)
    at model.ParallelClient.run(ParallelClient.java:25)
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = Thread-1
    at com.sun.glass.ui.Application.checkEventThread(Application.java:441)
    at com.sun.glass.ui.Screen.setEventHandler(Screen.java:369)
    at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:728)
    at javafx.stage.Screen.<clinit>(Screen.java:74)
    ... 4 more

Upvotes: 0

Views: 556

Answers (1)

James_D
James_D

Reputation: 209694

There are several problems with the structure of the application as you have posted it in the question.

The Application class represents the lifecycle of the entire application, which is managed via calls to its init(), start() and stop() methods. There should be only one Application instance in the entire application, and typically this instance is created by the JavaFX startup mechanism so you should not instantiate the Application subclass yourself.

JavaFX applications require the JavaFX runtime to be started, which includes launching the JavaFX Application Thread. This is done via a call to the static Application.launch() method, which must be called only once. The launch() method starts the JavaFX runtime, creates the instance of the Application class, calls init(), and then calls start() on the FX Application Thread. (In JavaFX 9 and later, you can also start the runtime by calling Platform.startup(), but use cases for this are rare).

Note that in your application, there is no call to Application.launch() (or Platform.startup()), so the JavaFX runtime is never started.

Certain operations can only be performed on the FX Application Thread. These include creating Stages and Scenes, and any modifications of properties of UI elements that are already displayed. Thus you cannot "run each client" in a separate thread. This is the cause of your exception: you are trying to create a new Stage on a thread that is not the FX Application Thread.

Each client does not need a new thread to display the UI (and, as described above, cannot do that). You likely do need to perform each client's communication with the server on a separate thread (because those are operations that take a long time, and you should not block the FX Application thread). You can do that by creating a new thread for each client's server communication, or using a shared executor service so that each client can get a thread from a pool (this is probably the preferred approach).

So your structure should look something like this:

public class Client {

    private Parent ui ;
    private ExecutorService exec ; // for handling server communication

    private final String user ;

    public Client(String user, ExecutorService exec) {
        this.user = user ;
        this.exec = exec ;
        try {
            ui = FXMLLoader.load(getClass().getResource("ui/client-management.fxml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Client(String user) {
        this(user, Executors.newSingleThreadExecutor());
    }

    public Parent getUI() {
        return ui ;
    }

    public void showInNewWindow() {
        Scene scene = new Scene(ui);
        Stage stage = new Stage();
        stage.setScene(scene);
        stage.show();
    }

    public void checkForNewEmail() {
        Task<List<Email>> newEmailTask = new Task<>() {
            @Override
            protected List<Email> call() throws Exception {
                List<Email> newEmails = new ArrayList<>();
                // contact server and retrieve any new emails
                return newEmails ;
            }
        };
        newEmailTask.setOnSucceeded(e -> {
            List<Email> newEmails = newEmailTask.getValue();
            // update UI with new emails...
        });
        exec.submit(newEmailTask);
    }

    // etc ...
}

Then your ClientController class could do something like this:

public class ClientController {

    private ExecutorService exec = Executors.newCachedThreadPool();

    public void startClients() {
        Client clientA = new Client("[email protected]", exec);
        Client clientB = new Client("[email protected]", exec);
        clientA.showInNewWindow();
        clientB.showInNewWindow();
    }
}

and your app class can do

public class ClientApp extends Application {

    @Override
    public void start(Stage primaryStage) {
        ClientController controller = new ClientController();
        controller.startClients();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

Upvotes: 4

Related Questions