Avatar billede fredand Forsker
08. februar 2013 - 11:09 Der er 8 kommentarer og
1 løsning

How to best practice update a label in swing vs javafx?

Hello!

I'm playing around with swing and javafx and updating a label from a runnable. It looks like you must do it in some other way with JavaFx since I get:
Not on FX application thread; currentThread = Thread-3
   

I compare these two examples.
First take a look at this swing example.
(It works but perhaps the approach might be wrong from the beginning?)

SWING:
package animationtest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class SwingTest extends JFrame
{
    JLabel label =  new JLabel("Hubba");
    JButton button =  new JButton("Click me");
    JPanel panel = new JPanel();
   
    MySwingRunnable myRunnable = new MySwingRunnable(label);
    final Thread taskThread = new Thread(myRunnable);
   
    public SwingTest()
    {
        button.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent ae)
            {
                taskThread.start();
            }
        }
        );
       
        panel.add(label);
        panel.add(button);
        getContentPane().add(panel);
    }
   
    public static void main(String[] args) {
        final SwingTest swingTest = new SwingTest();
           
        javax.swing.SwingUtilities.invokeLater( new Runnable()
        {
            public void run()
            {
                swingTest.setTitle("SwingTest");
                swingTest.pack();
                swingTest.validate();
                swingTest.setVisible(true);
            }
        });
       
    }
}

package animationtest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class SwingTest extends JFrame
{
    JLabel label =  new JLabel("Hubba");
    JButton button =  new JButton("Click me");
    JPanel panel = new JPanel();
   
    MySwingRunnable myRunnable = new MySwingRunnable(label);
    final Thread taskThread = new Thread(myRunnable);
   
    public SwingTest()
    {
        button.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent ae)
            {
                taskThread.start();
            }
        }
        );
       
        panel.add(label);
        panel.add(button);
        getContentPane().add(panel);
    }
   
    public static void main(String[] args) {
        final SwingTest swingTest = new SwingTest();
           
        javax.swing.SwingUtilities.invokeLater( new Runnable()
        {
            public void run()
            {
                swingTest.setTitle("SwingTest");
                swingTest.pack();
                swingTest.validate();
                swingTest.setVisible(true);
            }
        });
       
    }
}


JAVAFX
package animationtest;


import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class JavafxTest extends Application
{
    Label label =  new Label("Hubba");
    Button button =  new Button("Click me");
   
    MyJavafxRunnable myRunnable = new MyJavafxRunnable(label);
    final Thread taskThread = new Thread(myRunnable);
   
    public void start(final Stage primaryStage)
    {
        button.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(ActionEvent event)
            { 
                taskThread.start(); 
            }
        }
        );
         
        label.setLayoutX(70);
        label.setLayoutY(150);
       
        button.setLayoutX(100);
        button.setLayoutY(100);
        button.setText("Click me");
       
        final Group root = new Group();       
        root.getChildren().add(button);
        root.getChildren().add(label);
       

       
        Platform.runLater(new Runnable() {
           
            @Override
            public void run() {
                primaryStage.setTitle("Javafx test");
                primaryStage.setScene(new Scene(root, 300, 250));
                primaryStage.show();
               
            }
        });
 
    }
 
    public static void main(final String[] args) {
        launch(args);
    }
}


package animationtest;

import javafx.scene.control.Label;

public class MyJavafxRunnable implements Runnable{

    Label label;
    public MyJavafxRunnable(Label label)
    {
        this.label = label;
    }

    @Override
    public void run() {
       
        for(int i = 0; i < 1000000; i++)
        {
            label.setText( "no. " + i);
            //label.invalidate();
        }
       
    }

}


If I do it right in swing I guess it is supposed to be done in some other way with javafx?

Best regards
Fredrik
Avatar billede arne_v Ekspert
10. februar 2013 - 21:03 #1
I don't think the Swing code is "correct".

It should use EventQueue.invokeLater to actually change the label as Swing widgets are not threadsafe.

I JavaFX skal du bruge Platform.runLater.

The fun part is that you actually do use those two another place in the application.
Avatar billede fredand Forsker
11. februar 2013 - 11:41 #2
Hello Arne!

Thanks for yor reply!

First I tried to make my examples more alike one and other.


Swing
package animationtest;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class SwingTest {
    JLabel label = new JLabel("Hubba");
    JButton button = new JButton("Click me");
    JPanel panel = new JPanel();

    MySwingRunnable myRunnable = new MySwingRunnable(label);
    final Thread taskThread = new Thread(myRunnable);

    public SwingTest() {
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                taskThread.start();
            }
        });

        panel.add(label);
        panel.add(button);
       
        // javax.swing.SwingUtilities.invokeLater( new Runnable()
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                printThreadName("swing 3: ");
                JFrame jFrame = new JFrame();
                jFrame.getContentPane().add(panel);
                jFrame.pack();
                jFrame.validate();
                jFrame.setVisible(true);
            }
        });
    }

    public static void main(String[] args) {
        printThreadName("swing 1: ");
        SwingTest swingTest = new SwingTest();
    }

    public static void printThreadName(String prefix) {
        Thread thread = Thread.currentThread();
        String name = thread.getName();
        System.out.println(prefix + name);
    }
}

package animationtest;

import java.awt.EventQueue;

import javax.swing.JLabel;

public class MySwingRunnable implements Runnable {

    JLabel label;
    int counter = 0;
   
    public MySwingRunnable(JLabel label) {
        SwingTest.printThreadName("swing 2: ");
        this.label = label;
    }

    @Override
    public void run() {
        SwingTest.printThreadName("swing 4: ");
        for (counter = 0; counter < 100000; counter++) {
            //EventQueue.invokeLater( new Runnable()
            //{
            //public void run()
            //{
            SwingTest.printThreadName("swing 5: ");
            label.setText("no. " + counter);
            //}
            //});
        }
    }

}

FX
package animationtest;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class JavafxTest extends Application {
    Label label = new Label("Hubba");
    Button button = new Button("Click me");
    VBox vBox = new VBox();

    MyJavafxRunnable myRunnable = new MyJavafxRunnable(label);
    Thread thread = new Thread(myRunnable);

    public void start(final Stage primaryStage) {
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                thread.start();
            }
        });

        vBox.getChildren().add(button);
        vBox.getChildren().add(label);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                printThreadName("fx 3: ");
                final Group root = new Group();
                root.getChildren().add(vBox);
                Scene scene = new Scene(root, 300, 250);
                primaryStage.setScene(scene);
                primaryStage.show();
            }
        });
    }

    public static void main(final String[] args) {
        printThreadName("fx 1: ");
        launch(args);
    }

    public static void printThreadName(String prefix) {
        Thread thread = Thread.currentThread();
        String name = thread.getName();
        System.out.println(prefix + name);
    }
}

package animationtest;

import javafx.application.Platform;
import javafx.scene.control.Label;

public class MyJavafxRunnable implements Runnable {

    Label label;
    int counter = 0;

    public MyJavafxRunnable(Label label) {
        JavafxTest.printThreadName("fx 2: ");
        this.label = label;
    }

    @Override
    public void run() {
        JavafxTest.printThreadName("fx 4: ");
        for (counter = 0; counter < 100000; counter++) {
            Platform.runLater(new Runnable() {
                public void run() {
                    JavafxTest.printThreadName("fx 5: ");
                    label.setText("no. " + counter);
                }
            });
        }
    }
}

In the swing-example it works pretty well. The label seems to be rendered for each setText-call. How ever if I change it to be done in the "AWT-EventQueue-0" it just render a few calls.

In the fx-example it seems like I need to do de rendering in the "JavaFX Application Thread" but I just got it to render a few calls, as like the swing-example.

Perhaps this is the wrong approach with JavaFX?

Best regards
Fredrik
Avatar billede arne_v Ekspert
12. februar 2013 - 04:12 #3
I don't quite understand your code.

But I still believe that #1 is correct.

Swing example:


package february;

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class SwingLabelUpdate extends JFrame {
    private static final long serialVersionUID = 1L;
    private JLabel lbl;
    private JButton btn;
    public SwingLabelUpdate() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setLayout(new GridLayout(2, 1));
        setTitle("Label update");
        lbl = new JLabel("Click start to start");
        getContentPane().add(lbl);
        btn = new JButton("Start");
        btn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Thread t = new Thread(new Runnable() {
                    public void run() {
                        for(int i = 1; i <= 60; i++) {
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            final int i2 = i;
                            EventQueue.invokeLater(new Runnable() {
                                public void run() {
                                    lbl.setText(Integer.toString(i2));
                                }
                            });
                        }
                    }
                });
                t.start();
            }
        });
        getContentPane().add(btn);
        pack();
    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame f = new SwingLabelUpdate();
                f.setVisible(true);
            }
        });
    }
}


JavaFX example:


package february;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class JavaFXLabelUpdate extends Application {
    private Label lbl;
    private Button btn;
    @Override
    public void start(Stage stg) throws Exception {
        stg.setTitle("Label update");
        GridPane p = new GridPane();
        lbl = new Label("Click start to start");
        p.add(lbl, 0, 0);
        btn = new Button("Start");
        btn.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent ev) {
                Thread t = new Thread(new Runnable() {
                    public void run() {
                        for(int i = 1; i <= 60; i++) {
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            final int i2 = i;
                            Platform.runLater(new Runnable() {
                                public void run() {
                                    lbl.setText(Integer.toString(i2));
                                }
                            });
                        }
                    }
                });
                t.start();
            }
        });
        p.add(btn, 0, 1);
        Scene scene = new Scene(p);
        stg.setScene(scene);
        stg.sizeToScene();
        stg.show();
    }
    public static void main(String[] args) {
        Application.launch(args);
    }
}
Avatar billede fredand Forsker
12. februar 2013 - 11:10 #4
Hello Arne!
Thanks for your reply!

Really good examples.
Btw how to you style the code like that?
Really nice.

How ever I got some "spin-off-questions"

1) In the swing example I see that you use SwingUtilities.invokeLater(new Runnable() when the app starts.
For the setText you use EventQueue.invokeLater. What are the different? Would it be wrong to only use either one of them? Or is it better to use EventQueue for "small" events and SwingUtilities for "bigger" things like show the window?

2) Suppose you would like the counter run super fast from 0-100000.

If I then first change your code to (removing the sleep):
public void run() {
for (int i = 1; i <= 100000; i++) {
    final int i2 = i;
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            lbl.setText(Integer.toString(i2));
        }
    });
}
Then itlooks like it skips updating the screen for most of the  setText-calls.

If I then change it to
for (int i = 1; i <= 100000; i++) {
    final int i2 = i;
    lbl.setText(Integer.toString(i2));
}

Then it seems to be updating the screen for all setText-calls.

My question is then if the EventQueue.invokeLater is a must, since it seems to rationalize away most screen-updates?
Or is there a better way then doing it like my last change of your code?

3) In the FX example there seems be the same effect like my first change of your swing-example like:
for (int i = 1; i <= 100000; i++) {
    final int i2 = i;
    Platform.runLater(new Runnable() {
        public void run() {
            lbl.setText(Integer.toString(i2));
        }
    });
}
Then it also looks like it skips most updating of the screen for most of setText-calls.

And there seems not to be possible to run setText from other threads then from using Platform.runLater.

Then to run the FX-example super fast my conclusion is that in JavaFX you need to do it like this:
for (int i = 1; i <= 100000; i++) {
    try {
        Thread.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    final int i2 = i;
    Platform.runLater(new Runnable() {
        public void run() {
            lbl.setText(Integer.toString(i2));
        }
    });
}
Then it do not looks like any updating of the screen is skipped.
Do you agree?

Best regards
And thanks for your examples, I do think I learned some better way of implementing the code.
/Fredrik
Avatar billede arne_v Ekspert
12. februar 2013 - 15:20 #5
[ div ]
kode
[ / div ]

uden mellemrum
Avatar billede arne_v Ekspert
12. februar 2013 - 15:22 #6
re 1)

SwingUtilities.invokeLater og EventQueue.invokeLater goer fundamentalt det samme.

Jeg bruger altid den foerste i main og den anden i resten. Copy paste fra et eller andet.

http://stackoverflow.com/questions/8847083/swingutilities-invokelater-vs-eventqueue-invokelater

antyder at der er absolut ingen forskel.
Avatar billede arne_v Ekspert
12. februar 2013 - 15:26 #7
re 2 & 3)

Du tager vel 50-100 nanosekunder per gennemloeb af loekken. Det tager vel 10-20 millisekund eller deromkring at opdatere skaermen.

Du kan simpelthen ikke forvente at se noget.
Avatar billede fredand Forsker
12. februar 2013 - 20:18 #8
Hello Arne!

A big thank you for having this discussion whit me!

Please give a svar so I can reward you!

Best regards
Fredrik
Avatar billede arne_v Ekspert
12. februar 2013 - 22:14 #9
svar
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester