Updating GUI through threads

988 Views Asked by At

I'm fairly new to threads and haven't written Java in a long time so bear with me here. I have a very simple GUI. It has a counter, a status label, and two buttons, start and stop respectively.

What I wanted it to do was update my status label using a counter thread. When I hit start it supposedly starts the counter at 0 and increments it every second, and when I choose to hit stop it should suspend the current thread and wait for the start button to be pressed again. However whenever I hit stop it just suspends an waits for a second and resumes the counting. When in reality I want it to stay suspended. I'm not really sure why it's doing that, tried searching it before posting here but got nothing. Also feel free to criticize on anything you'd like.

Here's what I have:

UPDATED AS PER @MadProgrammer's answer.

import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class main extends JFrame 
{


    JLabel countLabel = new JLabel("0");
    JLabel statusLabel = new JLabel("Task not completed.");
    JButton startButton = new JButton("Start");
    JButton stopButton = new JButton("Stop");
    CounterThread worker = new CounterThread("worker", countLabel, statusLabel);

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new Main("Counter Demo");
            }
        });
    }

    public Main(String title) {
        super(title);

        setLayout(new GridBagLayout());

        countLabel.setFont(new Font("serif", Font.BOLD, 28));

        GridBagConstraints gc = new GridBagConstraints();

        gc.fill = GridBagConstraints.NONE;

        gc.gridx = 0;
        gc.gridy = 0;
        gc.weightx = 1;
        gc.weighty = 1;
        add(countLabel, gc);

        gc.gridx = 0;
        gc.gridy = 1;
        gc.weightx = 1;
        gc.weighty = 1;
        add(statusLabel, gc);

        gc.gridx = 0;
        gc.gridy = 2;
        gc.weightx = 1;
        gc.weighty = 1;
        add(startButton, gc);

        gc.gridx = 0;
        gc.gridy = 3;
        gc.weightx = 1;
        gc.weighty = 1;
        add(stopButton, gc);

        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                worker.start();
                //notify();
            }
        });
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                worker.suspend();
            }
        });
        setSize(200, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

public class CounterThread implements Runnable {

    public Thread t;
    public String threadName;
    boolean suspended = false;
    JLabel countLabelName;
    JLabel statusLabelName;

    CounterThread(String name, JLabel cLabel, JLabel sLabel) {
        this.threadName = name;
        this.countLabelName = cLabel;
        this.statusLabelName = sLabel;
    }

    public void run() {
        try {
            // Simulate doing something useful.
            for (int i = 0; i <= 10; i++) {
                synchronized (this) {
                    if (suspended) 
                    {             
                        wait();
                    }
                }
                final int count = i;

                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        countLabelName.setText(Integer.toString(count));
                    }
                });
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
        }

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                statusLabelName.setText("Completed.");
            }
        });

        this.start();
    }

    public boolean getStatus() {
        return t == null;
    }

    public void start() {
        if (getStatus()) {
            //t = new Thread(new CounterThread(this.threadName, this.countLabelName, this.statusLabelName));
            t = new Thread(this);
            t.start();
        }
    }

    public void suspend() {
        statusLabelName.setText("Task is paused");
        suspended = true;
    }
    //create an object whose only purpose is to synchronize

    synchronized void resume() {
        statusLabelName.setText("Task has resumed");
        suspended = false;
        this.notify();
    }

}
}
1

There are 1 best solutions below

5
On

Basically...

synchronized(this)
{
    if(suspended)
    {
        if(getStatus())
            wait();
        resume();
    }
}

getStatus is returning false so it's skipping the wait call (because t != null.

I'm not really sure why need to check this, but I might have a enum or other flag which returns a more meaningful state (like RUNNING, STOPPED, PAUSED ... what ever)

I was able to make it work by doing something like...

synchronized(this)
{
    if(suspended)
    {
        wait();
    }
}

instead.

Having said that though. I'd personally consider using a Swing Timer which would do all this work for you and would trigger it's updates within the context of the EDT

Updated after original code was modified

Even with your suggested answer it's still behaving the same way, it suspends it for a brief second and resumes right away

You modified the code from the original post, adding

t = new Thread(new CounterThread(this.threadName, this.countLabelName, this.statusLabelName));

to the start method, but your UI code already has a reference to CounterThread which it's interacting with, so now you have two instances of the same class, one which is running in the background ticking away and one which your UI code is interacting with.

So when the UI calls suspend, it's not changing the suspended state of the instance which is actually running

import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class Main extends JFrame {

    JLabel countLabel = new JLabel("0");
    JLabel statusLabel = new JLabel("Task not completed.");
    JButton startButton = new JButton("Start");
    JButton stopButton = new JButton("Stop");
    int holder;
    CounterThread worker = new CounterThread("worker", countLabel, statusLabel);

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new Main("Counter Demo");
            }
        });
    }

    public Main(String title) {
        super(title);

        setLayout(new GridBagLayout());

        countLabel.setFont(new Font("serif", Font.BOLD, 28));

        GridBagConstraints gc = new GridBagConstraints();

        gc.fill = GridBagConstraints.NONE;

        gc.gridx = 0;
        gc.gridy = 0;
        gc.weightx = 1;
        gc.weighty = 1;
        add(countLabel, gc);

        gc.gridx = 0;
        gc.gridy = 1;
        gc.weightx = 1;
        gc.weighty = 1;
        add(statusLabel, gc);

        gc.gridx = 0;
        gc.gridy = 2;
        gc.weightx = 1;
        gc.weighty = 1;
        add(startButton, gc);

        gc.gridx = 0;
        gc.gridy = 3;
        gc.weightx = 1;
        gc.weighty = 1;
        add(stopButton, gc);

        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                worker.start();
            }

        });

        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                worker.suspend();
            }
        });
        setSize(200, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }

    public class CounterThread implements Runnable {

        public Thread t;
        public String threadName;
        boolean suspended = false;
        JLabel countLabelName;
        JLabel statusLabelName;

        CounterThread(String name, JLabel cLabel, JLabel sLabel) {
            this.threadName = name;
            this.countLabelName = cLabel;
            this.statusLabelName = sLabel;
        }

        public void run() {
            try {
                // Simulate doing something useful.
                for (int i = 0; i <= 10; i++) {
                    synchronized (this) {
                        if (suspended) {
                            wait();
                        }
                    }
                    final int count = i;

                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            countLabelName.setText(Integer.toString(count));
                        }
                    });

                    Thread.sleep(1000);

                }
            } catch (InterruptedException e) {
            }

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    statusLabelName.setText("Completed.");
                }
            });

            this.start();
        }

        public boolean getStatus() {
            return t == null;
        }

        public void start() {
            if (getStatus()) {
                //t = new Thread(new CounterThread(this.threadName, this.countLabelName, this.statusLabelName));
                t = new Thread(this);
                t.start();
            }
        }

        public void suspend() {
            statusLabelName.setText("Task is paused");
            suspended = true;
        }
        //create an object whose only purpose is to synchronize

        synchronized void resume() {
            statusLabelName.setText("Task has resumed");
            suspended = false;
            this.notify();
        }

    }
}

Also, I don't see how using a Swing Timer would help me in this case, since there's no actual delay for the wait

Then you clearly don't understand how a Timer works

import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Main extends JFrame {

    JLabel countLabel = new JLabel("0");
    JLabel statusLabel = new JLabel("Task not completed.");
    JButton startButton = new JButton("Start");
    JButton stopButton = new JButton("Stop");
    int holder;

    Timer timer;
    int count = 0;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new Main("Counter Demo");
            }
        });
    }

    public Main(String title) {
        super(title);

        setLayout(new GridBagLayout());

        countLabel.setFont(new Font("serif", Font.BOLD, 28));

        GridBagConstraints gc = new GridBagConstraints();

        gc.fill = GridBagConstraints.NONE;

        gc.gridx = 0;
        gc.gridy = 0;
        gc.weightx = 1;
        gc.weighty = 1;
        add(countLabel, gc);

        gc.gridx = 0;
        gc.gridy = 1;
        gc.weightx = 1;
        gc.weighty = 1;
        add(statusLabel, gc);

        gc.gridx = 0;
        gc.gridy = 2;
        gc.weightx = 1;
        gc.weighty = 1;
        add(startButton, gc);

        gc.gridx = 0;
        gc.gridy = 3;
        gc.weightx = 1;
        gc.weighty = 1;
        add(stopButton, gc);

        timer = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                count++;
                countLabel.setText(Integer.toString(count));
            }
        });

        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                timer.start();
            }

        });

        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                if (timer.isRunning()) {
                    timer.stop();
                    stopButton.setText("Resume");
                } else {
                    timer.restart();
                    stopButton.setText("Stop");
                }
            }
        });
        setSize(200, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);

    }
}

Now, there is the pause and resume issues all taken care off