1

What is the recommended way to display multiple messages in GUI in JavaFX?

The obvious thing would be to create a TextArea and just append messages. But since I have them generated during some CPU intensive computations, they come from a different thread. In one of many answers I read that TextArea should be accessed only from Main GUI Thread, so I used LinkedBlockingQueue as a Producer/Consument interface. But when I tried to append messages using Timer's scheduleAtFixedRate, I ran into more errors, like this:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at com.sun.javafx.text.PrismTextLayout.getRuns(PrismTextLayout.java:236)
    at javafx.scene.text.Text.getRuns(Text.java:317)
    at javafx.scene.text.Text.updatePGText(Text.java:1465)
    at javafx.scene.text.Text.impl_updatePeer(Text.java:1500)
    at javafx.scene.Node.impl_syncPeer(Node.java:503)
    at javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2290)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2419)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2289)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2419)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)

I think the problem may be flooding the FX Application Thread with too many messages. But there must be some recommended way to do this. Right?

MCVE:

public class MCVE extends Application {    
    @Override
    public void start(Stage primaryStage) {
        TextArea ta = new TextArea();
        StackPane root = new StackPane();
        root.getChildren().add(ta);
        Scene scene = new Scene(root, 800, 650);
        primaryStage.setScene(scene);
        primaryStage.show();

        Run.ta = ta;
        new Thread(new Run()).start();

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

    static class Run implements Runnable{
        public static TextArea ta;
        @Override
        public void run() {
            for (int i = 0; i < 1000000000; i++) {
                ta.appendText("lahsdvl lefwq gwlqwkjgl kqwldfwkhevf.");
            }
        }
    }
}

If you add Strings to BlockingQueue (instead of appending in Run) and then drain & poll them in Application Thread, you still get the same Exception.

alex
  • 10,900
  • 15
  • 70
  • 100

1 Answers1

1

You can use Platform#runLater(Runnable) to update the GUI from another thread:

Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater. If this method is called after the JavaFX runtime has been shutdown, the call will be ignored: the Runnable will not be executed and no exception will be thrown.

NOTE: applications should avoid flooding JavaFX with too many pending Runnables. Otherwise, the application may become unresponsive. Applications are encouraged to batch up multiple operations into fewer runLater calls. Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.

Adapt Run as follows:

static class Run implements Runnable {

    public static TextArea ta;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Platform.runLater(() -> ta.appendText("Lorem ipsum.\n"));
        }
    }

}

Also have a look at these related questions:

Community
  • 1
  • 1
beatngu13
  • 7,201
  • 6
  • 37
  • 66