javajavafxpaneswingutilities

Javafx modifying "Label" in SwingUtilities.invokeLater


I wanted to do the following:

  1. click a button
  2. colors a rectangle red
  3. waits 1 sec
  4. colors a rectangle blue

In the following code:


GraphicsContext gc;
Button myButton = new Button("Button!");
myButton.setOnAction(new EventHandler<ActionEvent>(){
        @Override
        public void handle(ActionEvent event){
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    gc.setFill(Color.RED);
                    gc.fillRect(0, 0, 100, 100);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                    gc.setFill(Color.BLUE);
                    gc.fillRect(0, 0, 100, 100);
                }
            });
        }
    });

This works fine. It creates a red box at 0,0 with width & height of 100. But when I try to modify say, a Label, it crashes.


GraphicsContext gc;
Pane root = new Pane();
Button myButton = new Button("Button!");
Label myLabel = new Label("HELLO!"); // added
root.getChildren.add(myLabel);
myButton.setOnAction(new EventHandler<ActionEvent>(){
        @Override
        public void handle(ActionEvent event){
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    gc.setFill(Color.RED);
                    gc.fillRect(0, 0, 100, 100);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                    gc.setFill(Color.BLUE);
                    gc.fillRect(0, 0, 100, 100);
                    myLabel.setText("WORLD!"); // modified here
                }
            });
        }
    });

The result I'm expecting is,

  1. click a button

  2. colors a rectangle red

  3. waits 1 sec

  4. colors a rectangle blue

  5. then changes the label text from HELLO! to WORLD!

But I'm getting an error. Why is this happening? Can I not modify any Pane elements in SwingUtilities?

Any advice will be helpful.


EDIT

This is the error I get when I press the button

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:229)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.scene.control.Labeled.setText(Labeled.java:145)
at Lego$1$1.run(Lego.java:63)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

Solution

  • This is because any Java FX components must be managed by the JavaFX Application Thread due to the fact that they are not thread-safe (this is for the exact same reason that any Swing components must be managed by the AWT event dispatching Thread), so you need to modify your label indirectly using Platform.runLater(runnable) as next:

    With Java 8

    Platform.runLater(() -> myLabel.setText("WORLD!"));
    

    With previous versions of Java

    Platform.runLater(new Runnable() {
            public void run() {
                myLabel.setText("WORLD!"); 
            }
        }
    );
    

    We use SwingUtilities.invokeLater(runnable) to make the AWT event dispatching thread execute some code that will modify Swing components at some unspecified time in the future, it is the same idea with Platform.runLater(runnable) in case of Java FX components.

    NB: Use only Swing components or only Java FX components, avoid mixing Swing and JavaFX components unless you have no other choices.