Riven Lv 3 Riven Lv 3 - 1 month ago 7
Java Question

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)

Answer

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 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:

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

We use SwingUtilities.invokeLater(runnable) to execute some code by the AWT event dispatching thread when you want to modify Swing components, it is the same idea with Platform.runLater(runnable) in case of Java FX components.

NB: Avoid mixing Swing and JavaFX components unless you have no other choices.