user3487276
user3487276

Reputation: 99

How can I use concurrency (threads) in JavaFX (with FXML!!! (JavaFX Scene Builder) )?

How can I use threads in JavaFX with FXML and the task or service class?

I need concurrency in my program, because I use a very long loop. If I code this "manually" (without FXML) then it works. But with FXML it does not work (JavaFX Scene Builder). Instead of printing out the "String buffer" variable in the console I want to write it in the textarea (which is an updatable component). But in this case, of course, an error message appears, because it no longer belongs to the JavaFX Application Thread. How can I make this component (TextArea textAreaAusgabe) update / updatable, so that I can use it in threads ? And sorry for my bad English ? Hopefully you know what my problem is (It's FXML :-D). If there is another gui builder like in NetBeans the gui builder for swing I would use it. As far as I know is the Scene Builder the one and only gui builder for JavaFX :-(.

I want to use task3 in my code snippet. Here is my code snippet which does not work (if I use components like labels, textfields etc.):

import java.net.URL; 
import java.util.ResourceBundle; 
import javafx.fxml.Initializable; 
import javafx.scene.control.Button; 
import javafx.scene.control.TextArea; 
import java.io.*; 
import java.util.Random; 
import java.util.Arrays; 
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileSystemView;
public class Kontroller implements Initializable 
{ 

    int array [ ] = new int [50]; 
    int counter = 0; 

    String buffer; 

    int z; 

    public TextArea textAreaAusgabe; 
    public Button buttonBerechnen; 
    public Button buttonReset; 
    public Label labelAnzahl; 
    public ProgressBar progressBar; 
    public ProgressIndicator progressIndikator; 

    public CheckBox checkBox1; 
    public CheckBox checkBox2; 
    public CheckBox checkBox3; 
    public CheckBox checkBox4; 
    public CheckBox checkBox5; 
    public CheckBox checkBox6; 
    public CheckBox checkBox7; 
    public CheckBox checkBox8; 
    public CheckBox checkBox9; 
    public CheckBox checkBox10; 

    Task <Integer> task = new Task <Integer> ( ) 
    { 
        @Override 
        protected Integer call ( ) throws Exception 
        { 
            int i; 
            for (i = 0; i < 100; i++) 
            { 
                if (isCancelled ( ) ) 
                { 
                    break; 
                } 
                System.out.println("Anzahl " + i); 
            } 
            return i; 
        } 
    }; 

    Task <Integer> task2 = new Task <Integer> ( ) 
    { 
        @Override 
        protected Integer call ( ) throws Exception 
        { 
            int j; 
            for (j = 0; j < 1000; j++) 
            { 
                if (isCancelled ( ) ) 
                { 
                    updateMessage ("Abgebrochen"); 
                    break; 
                } 
                System.out.println ("Anzahl " + j); 
                updateMessage ("Anzahl " + j); 
                updateProgress (j, 1000); 
                try 
                { 
                    Thread.sleep (1); 
                } catch (InterruptedException interrupted) 
                { 
                    if (isCancelled ( ) ) 
                    { 
                        updateMessage ("Abgebrochen"); 
                        break; 
                    } 
                } 
            } 
            return j; 
        } 
    }; 

    Service <Void> service = new Service <Void> ( ) 
    { 
        @Override 
        protected Task <Void> createTask ( ) 
        { 
            return new Task <Void> ( ) 
            { 
                @Override 
                protected Void call ( ) throws Exception 
                { 
                    return null; 
                } 
            }; 
        } 
        @Override 
        protected void succeeded ( ) 
        { 

        } 
    }; 

    Task task3 = new Task <Void> ( ) 
    { 
        @Override public Void call ( ) throws IOException 
        { 
            File ergebnis = new File (FileSystemView.getFileSystemView ( ).getHomeDirectory ( ).getAbsolutePath ( ) + "\\Ergebnis.txt"); 
            final FileWriter fw = new FileWriter (ergebnis); 

            for (int k = 1; k <= 10000000; k++) 
            { 
                if (isCancelled ( ) ) 
                { 
                    break; 
                } 
                updateProgress (k, 10000000); 
                Arrays.fill (array, 0); 

            // textAreaAusgabe.appendText (buffer); // Does not work 
            System.out.println (buffer); // Works 
            System.out.println (counter++); 
            System.out.println ( ); 
            fw.append (System.getProperty ("line.separator") ); 
            fw.append (System.getProperty ("line.separator") ); 
            fw.append (""+counter+""); 
            fw.append (System.getProperty ("line.separator") ); 
            fw.append (System.getProperty ("line.separator") ); 

            } 
            return null; 
        } 
    }; 

    Thread thread = new Thread (task3); 

    public void reset ( ) 
    { 
        // Zurücksetzen 
    } 
    public void berechnen (ActionEvent event) 
    { 
        thread.setDaemon (true); 
        thread.start ( ); 
    } 
    public void fehler ( ) 
    { 
        // Zeige Stage bei Fehler 
    } 
    public void initialize (URL url, ResourceBundle rb) 
    { 
        progressBar.progressProperty ( ).bind (task3.progressProperty ( ) ); 
        progressIndikator.progressProperty ( ).bind (task3.progressProperty ( ) ); 
    } 
} 

If I comment out the line:

// textAreaAusgabe.appendText (buffer); // Does not work 

The following error appears:

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4

And this is the complete error message in NetBeans output:

ant -f D:\\Users\\Benutzer\\Documents\\NetBeansProjects\\Threading jfxsa-run
init:
Deleting: D:\Users\Benutzer\Documents\NetBeansProjects\Threading\build\built-jar.properties
deps-jar:
Updating property file: D:\Users\Benutzer\Documents\NetBeansProjects\Threading\build\built-jar.properties
Compiling 1 source file to D:\Users\Benutzer\Documents\NetBeansProjects\Threading\build\classes
warning: [options] bootstrap class path not set in conjunction with -source 1.5
warning: [options] source value 1.5 is obsolete and will be removed in a future release
warning: [options] target value 1.5 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
4 warnings
compile:
Detected JavaFX Ant API version 1.3
Launching <fx:jar> task from F:\Java\Java Development Kit\Java Development Kit (1.8.0_20) (x64)\jre\..\lib\ant-javafx.jar
Warning: From JDK7u25 the Codebase manifest attribute should be used to restrict JAR repurposing.
         Please set manifest.custom.codebase property to override the current default non-secure value '*'.
Launching <fx:deploy> task from F:\Java\Java Development Kit\Java Development Kit (1.8.0_20) (x64)\jre\..\lib\ant-javafx.jar
jfx-deployment-script:
jfx-deployment:
jar:
Copying 12 files to D:\Users\Benutzer\Documents\NetBeansProjects\Threading\dist\run1389427067
jfx-project-run:
Executing D:\Users\Benutzer\Documents\NetBeansProjects\Threading\dist\run1389427067\Threading.jar using platform F:\Java\Java Development Kit\Java Development Kit (1.8.0_20) (x64)\jre/bin/java
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
    at javafx.scene.Scene.addToDirtyList(Scene.java:485)
    at javafx.scene.Node.addToSceneDirtyList(Node.java:424)
    at javafx.scene.Node.impl_markDirty(Node.java:415)
    at javafx.scene.shape.Shape.impl_markDirty(Shape.java:942)
    at javafx.scene.Node.impl_geomChanged(Node.java:3784)
    at javafx.scene.text.Text.impl_geomChanged(Text.java:763)
    at javafx.scene.text.Text.needsTextLayout(Text.java:194)
    at javafx.scene.text.Text.needsFullTextLayout(Text.java:189)
    at javafx.scene.text.Text.access$200(Text.java:96)
    at javafx.scene.text.Text$2.invalidated(Text.java:386)
    at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:109)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:143)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
    at javafx.scene.text.Text.setText(Text.java:367)
    at com.sun.javafx.scene.control.skin.TextAreaSkin.lambda$new$231(TextAreaSkin.java:571)
    at com.sun.javafx.scene.control.skin.TextAreaSkin$$Lambda$256/235779996.invalidated(Unknown Source)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:349)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.scene.control.TextInputControl$TextProperty.fireValueChangedEvent(TextInputControl.java:1123)
    at javafx.scene.control.TextInputControl$TextProperty.markInvalid(TextInputControl.java:1127)
    at javafx.scene.control.TextInputControl$TextProperty.invalidate(TextInputControl.java:1066)
    at javafx.scene.control.TextInputControl$TextProperty.access$1300(TextInputControl.java:1038) 

Upvotes: 0

Views: 3040

Answers (1)

jewelsea
jewelsea

Reputation: 159341

Your issue is not FXML specific.

Your issue is that your Task code violates JavaFX threading rules (i.e. you cannot modify a node in an active scene off of the JavaFX application thread).

Replace the code which does not work:

textAreaAusgabe.appendText (buffer); // Does not work 

With:

Platform.runLater(() -> textAreaAusgabe.appendText (buffer));

This will execute the manipulation of the text area node on the JavaFX application thread and solve your thread runtime problem.

See the Task documentation section "A Task Which Modifies The Scene Graph".

However, with your sample application, you will now run into another issue as you will be updating the JavaFX scene rapidly one hundred million times, which will overload the JavaFX system and make your application unresponsive. What you will need to do is batch up your text changes and only issue the runlater call to update the UI no more than 60 times per second. Asking the UI to update more than 60 times a second is useless anyway as nobody can see the intermediate updates.

Also, to answer your ancilliary question => No, there is currently no other GUI editor for JavaFX than SceneBuilder.

Upvotes: 2

Related Questions