Using a thread loop to update a JFrame

13,725

Solution 1

In order for Tassos' answer to work, you actually have to create an new thread, which you did not do. Simply calling

ThreadTester example = new ThreadTester(2,this);
example.run();

is not enough, sice that just calls the run method from EDT. You need to do the following:

Thread t = new Thread(new ThreadTester(2,this));
t.start();

Please refer to Defining and Starting a Thread.

Also, you want modify the same field from two different threads (runnable), which is a bug. You should read more about java concurrency.

Solution 2

The problem is that you are blocking the EDT (Event Dispatching Thread), preventing the UI to refresh until your loop is finished.

The solutions to these issues is always the same, use a Swing Timer or use a SwingWorker.

Here is an example of the usage of a SwingWorker:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class TestSwingWorker {

    private JTextField progressTextField;

    protected void initUI() {
        final JFrame frame = new JFrame();
        frame.setTitle(TestSwingWorker.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton button = new JButton("Clik me to start work");
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                doWork();
            }
        });
        progressTextField = new JTextField(25);
        progressTextField.setEditable(false);
        frame.add(progressTextField, BorderLayout.NORTH);
        frame.add(button, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    protected void doWork() {
        SwingWorker<Void, Integer> worker = new SwingWorker<Void, Integer>() {
            @Override
            protected Void doInBackground() throws Exception {
                // Here not in the EDT
                for (int i = 0; i < 100; i++) {
                    // Simulates work
                    Thread.sleep(10);
                    publish(i); // published values are passed to the #process(List) method
                }
                return null;
            }

            @Override
            protected void process(List<Integer> chunks) {
                // chunks are values retrieved from #publish()
                // Here we are on the EDT and can safely update the UI
                progressTextField.setText(chunks.get(chunks.size() - 1).toString());
            }

            @Override
            protected void done() {
                // Invoked when the SwingWorker has finished
                // We are on the EDT, we can safely update the UI
                progressTextField.setText("Done");
            }
        };
        worker.execute();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TestSwingWorker().initUI();
            }
        });
    }
}

Solution 3

Change this line

    myMainForm.setTextBox(i + "counter");

into

final String var = i + "counter";
java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
                    myMainForm.setTextBox(var);
        }
    });
}

Why? Because you can't do UI work in non-UI threads.

Share:
13,725
Festivejelly
Author by

Festivejelly

I make games and test stuff :)

Updated on June 04, 2022

Comments

  • Festivejelly
    Festivejelly almost 2 years

    ive done some extensive searching on using threads in a loop and whilst I understand the concept how how seperate threads work, I still cant seem to grasp how to implement it in my simple application.

    My application consists of a form with a text box. This textbox needs to be updated once ever iteration of a loop. It starts with the press of a button but the loop should also finish with the press of a stop button. Ive used a boolean value to track if its been pressed.

    Here is my form code:

    package threadtester;
    
    public class MainForm extends javax.swing.JFrame {
    
        public MainForm() {
            initComponents();
        }
    
        private void RunButtonActionPerformed(java.awt.event.ActionEvent evt) {
           ThreadTester.setRunnable(true);
           ThreadTester example = new ThreadTester(2,this);
           example.run();
        }
    
        private void StopButtonActionPerformed(java.awt.event.ActionEvent evt) {
           ThreadTester.setRunnable(false);
        }
    
    
        public static void main(String args[]) {
    
        java.awt.EventQueue.invokeLater(new Runnable() {
                public void run() {
                    new MainForm().setVisible(true);
                }
            });
        }
    
        public void setTextBox(String myString){
        MainTextbox.setText(myString);
        }
    
    }
    

    As you can see I have a button that is pressed. When the button is pressed this executes the code thats in a different class called ThreadTester. Here is the code for that class:

    package threadtester;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    public class ThreadTester implements Runnable
    {
        int thisThread;
        MainForm myMainForm;
        private static boolean runnable;
        // constructor
        public ThreadTester (int number,MainForm mainForm)
        {
            thisThread = number;
            myMainForm = mainForm;   
        }
    
        public void run ()
        {
        for (int i =0;i< 20; i++) {
            if(runnable==false){
               break;
            } 
            System.out.println("I'm in thread " + thisThread + " line " + i);
            myMainForm.setTextBox(i + "counter");
            try {
                   Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(ThreadTester.class.getName()).log(Level.SEVERE, null, ex);
            }
        } }
    
        public static void setRunnable(Boolean myValue){
           runnable = myValue;
        }
    
        public static void main(String[] args) {
    
            MainForm.main(args);
        }  
    }
    

    as you can see the loop has been created on a seperate thread... but the textbox only updates after the loop has finished. Now as far as im aware in my MainForm I created a seperate thread to run the loop on, so I dont understand why its not running? Any guidence would be much appreciated, ive tried looking at examples on stack exchange but I cant seem to get them to fit into my implemntation.

    With the recommendation suggested by Tassos my run method now looks like this:

    public void run ()
    {
    for (int i =0;i< 20; i++) {
        if(runnable==false){
            break;
        }
        System.out.println("I'm in thread " + thisThread + " line " + i);
    
        final String var = i + "counter";
            java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                    myMainForm.setTextBox(var);
            }
        });
    
    
        try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(ThreadTester.class.getName()).log(Level.SEVERE, null, ex);
            }
    } }