Alex
Alex

Reputation: 1749

Multithreaded JavaFX UI

I have two multithreading issues that are interwoven.

I have a SplitPane in a JavaFX desktop application using spring boot. On the left is a TreeView, on the right a TabPane. In the beginning the SplitPane devider is at the right edge hiding the TabPane. When the user selects an item in the tree two things shall happen in parallel:

First, depending on the selection ~10 tabs are created. The tabs differ depending on the selected object. Each tab shows detail data related to the selected object. Most tabs contain charts but also text.

Second, since the setup of the tabs takes a few seconds - data needs to be fetched from the DB - the idea was to "roll" the TabPane devider slowly to the left side, hiding the tree. This gives the user the impression that the application is still running and makes the transition softer. I don't like waiting, waiting,.. bang!

One trouble is, the Timeline (see below) I am using is stuttering and I can't make it work seamless in parallel to setup the tabs.

The creation of the tabs runs in a dedicated Task and each tab creator uses Platform.runLater(). Second trouble is, I am not sure whether this is the best way to code this. I mean, almost all tabs are hidden by the top one but all tabs are created one after the other in the application thread.

In other words, the pseudo code would look something like this:

KeyValue keyValue = new KeyValue(splitPane.getDividers().get(0).positionProperty(), 0.1);
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(300), keyValue));
timeline.play();

Task task = new Task<Void>() {
    @Override public Void call() {
       Platform.runLater(()-> {
           createTabA();
       }

       ..

       Platform.runLater(()-> {
           createTabZ();
       }

       return null;
    }
};
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();

I also changed the order, having the Timeline after starting the Thread. It looks different but still not as expected.

How can I improve my multi-threading logic and get rid of the bucking?

BTW: In the real code I am already changing the cursor when this process runs, to make it more digestible for the user.

Thank you in advance!

Upvotes: 0

Views: 150

Answers (1)

fabian
fabian

Reputation: 82451

Runnables passed to Platform.runLater run on the JavaFX application thread and using multiple calls of Platform.runLater from a background thread in rapid succession has the only benefit that JavaFX could decide to do layout between the Tab creations. The tab creations don't get and faster.

Runnables passed to Platform.runLater shouldn't contain any long-running operations.

Instead create the tabs on the background thread but don't attach them to the scene from this thread. (Creating (parts of) a scene on a background thread is not an issue as long as you don't modify something that is attached to a Scene.)

If it's ok for you to add all the tabs at once at the end of the task, consider using a Task<List<Tab>> and returning the tabs.

Task<List<Tab>> task = new Task<List<Tab>>() {
    @Override public List<Tab> call() {
       List<Tab> result = new ArrayList<>(1 + 'Z' - 'A');

       for (char c = 'A'; c <= 'Z'; c++) {
           result.add(createTab(c));
       }

       return result;
    }
};
task.setOnSucceeded(evt -> tabPane.getTabs().addAll(task.getValue()));
...

Otherwise create the Tabs on the background thread, but add them to the TabPane on the application thread:

Task<Void> task = new Task<Void>() {
    @Override public Void call() {
       for (char c = 'A'; c <= 'Z'; c++) {
           final Tab tab = createTab(c);
           Platform.runLater(() -> tabPane.getTabs().add(tab));
       }

       return null;
    }
};

Upvotes: 1

Related Questions