Search code examples
javamultithreadingswingevent-dispatch-thread

Why is my thread not working properly in Swing?


I am printing simple value to append JTextArea using simple for loop, and when I run it, it's properly Run if I print value in console output...

But if I append JTextArea and print value in the text area, they are appended all after whole program run.

public class SwingThread {

private JFrame frame;

/**
 * Launch the application.
 */
public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                SwingThread window = new SwingThread();
                window.frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

/**
 * Create the application.
 */
public SwingThread() {
    initialize();
}

/**
 * Initialize the contents of the frame.
 */
private void initialize() {
    frame = new JFrame();
    frame.setBounds(100, 100, 450, 300);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JScrollPane scrollPane = new JScrollPane();
    frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

    JTextArea textArea = new JTextArea();
    scrollPane.setViewportView(textArea);

    JButton btnNewButton = new JButton("New button");
    scrollPane.setColumnHeaderView(btnNewButton);
    btnNewButton.addActionListener(new ActionListener()
    {
        public void actionPerformed(ActionEvent arg0)
        {
            try
            {
                for(int i = 0 ; i <= 5 ; i++)
                {
                    textArea.append("Value "+i+"\n");
                    System.out.println("Value is" + i);
                    Thread.sleep(1000);
                }
            }
            catch(Exception e)
            {
                System.out.println("Error : "+e);
            }
        }
    });
}
}

I want to append one by one, but it was appended after the whole program runs.


Solution

  • Your problem is with your use of Thread.sleep, since when you call this on the Swing event thread (or EDT for Event Dispatch Thread) as you are doing, it will put the entire Swing event thread to sleep. When this happens the actions of this thread cannot be performed, including painting the GUI (updating it) and interacting with the user, and this will completely freeze your GUI -- not good. The solution in this current situation is to use a Swing Timer as a pseudo-loop. The Timer creates a loop within a background thread and guarantees that all code within its actionPerformed method will be called on the Swing event thread, a necessity here since we don't want to append to the JTextArea off of this thread.

    Also as others have noted, if all you want to do is to perform a repeated action with delay in Swing, then yes, use this Swing Timer. If on the other hand you wish to run a long-running bit of code in Swing, then again this code will block the EDT and will freeze your program. For this situation use a background thread such as one supplied by a SwingWorker. Please check out Lesson: Concurrency in Swing for more on this.

    e.g.,

    btnNewButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
            // delay between timer ticks: 1000
            int timerDelay = 1000;
            new Timer(timerDelay, new ActionListener() {
                private int counter = 0;
                @Override
                public void actionPerformed(ActionEvent e) {
                    // timer's stopping condition
                    if (counter >= MAX_VALUE) { // MAX_VALUE is a constant int = 5
                        ((Timer) e.getSource()).stop();
                    } else {
                        textArea.append("Value " + counter + "\n");
                    }
                    counter++; // increment timer's counter variable
                }
            }).start();
        }
    });
    

    The whole thing:

    import java.awt.BorderLayout;
    import java.awt.EventQueue;
    import java.awt.event.*;
    
    import javax.swing.*;
    
    public class SwingThread2 {
        protected static final int MAX_VALUE = 5; // our constant
        private JFrame frame;
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        SwingThread2 window = new SwingThread2();
                        window.frame.setVisible(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        public SwingThread2() {
            initialize();
        }
    
        private void initialize() {
            frame = new JFrame();
            // frame.setBounds(100, 100, 450, 300); // avoid this
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            JScrollPane scrollPane = new JScrollPane();
            frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
    
            JTextArea textArea = new JTextArea(15, 40);
            scrollPane.setViewportView(textArea);
    
            JButton btnNewButton = new JButton("New button");
            scrollPane.setColumnHeaderView(btnNewButton);
            btnNewButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent arg0) {
                    // delay between timer ticks: 1000
                    int timerDelay = 1000;
                    new Timer(timerDelay, new ActionListener() {
                        private int counter = 0;
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            // timer's stopping condition
                            if (counter >= MAX_VALUE) { // MAX_VALUE is a constant int = 5
                                ((Timer) e.getSource()).stop();
                            } else {
                                textArea.append("Value " + counter + "\n");
                            }
                            counter++; // increment timer's counter variable
                        }
                    }).start();
                }
            });
    
            // better to avoid setting sizes but instead to
            // let the components size themselves vis pack
            frame.pack();
            frame.setLocationRelativeTo(null);
        }
    }
    

    Just for further information, here is an example of the same program above that uses a SwingWorker to perform a long running action, and then update a JProgressBar with this action. The worker is quite simple, and simply uses a while loop to advance a counter variable by a bounded random amount. It then transmits uses this value to update its own progress property (a value that can only be from 0 to 100, and so in other situations, the value will need to be normalized to comply with this). I attach a PropertyChangeListener to the worker, and this is notified on the Swing event thread whenever the worker's progress value changes and also whenever the SwingWorker changes state, such as when it is done operating. In the latter situation, the worker's StateValue becomes StateValue.DONE. The listener then updates the GUI accordingly. Please ask if any questions.

    import java.awt.BorderLayout;
    import java.awt.EventQueue;
    import java.awt.event.*;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.util.Random;
    import java.util.concurrent.ExecutionException;
    
    import javax.swing.*;
    
    public class SwingThread2 {
        protected static final int MAX_VALUE = 5; // our constant
        private JFrame frame;
        private JProgressBar progressBar = new JProgressBar();
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        SwingThread2 window = new SwingThread2();
                        window.frame.setVisible(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        public SwingThread2() {
            initialize();
        }
    
        private void initialize() {
            frame = new JFrame();
            // frame.setBounds(100, 100, 450, 300); // avoid this
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            JScrollPane scrollPane = new JScrollPane();
            frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
    
            JTextArea textArea = new JTextArea(15, 40);
            scrollPane.setViewportView(textArea);
    
            JButton btnNewButton = new JButton("New button");
            scrollPane.setColumnHeaderView(btnNewButton);
            btnNewButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent arg0) {
                    // delay between timer ticks: 1000
                    int timerDelay = 1000;
                    new Timer(timerDelay, new ActionListener() {
                        private int counter = 0;
    
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            // timer's stopping condition
                            if (counter >= MAX_VALUE) { // MAX_VALUE is a constant
                                                        // int = 5
                                ((Timer) e.getSource()).stop();
                            } else {
                                textArea.append("Value " + counter + "\n");
                            }
                            counter++; // increment timer's counter variable
                        }
                    }).start();
                }
            });
    
            progressBar.setStringPainted(true);
            JPanel bottomPanel = new JPanel();
            bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
            bottomPanel.add(new JButton(new MyAction("Press Me")));
            bottomPanel.add(progressBar);
    
            frame.getContentPane().add(bottomPanel, BorderLayout.PAGE_END);
    
            // better to avoid setting sizes but instead to
            // let the components size themselves vis pack
            frame.pack();
            frame.setLocationRelativeTo(null);
        }
    
        private class MyAction extends AbstractAction {
            public MyAction(String name) {
                super(name);
                int mnemonic = (int) name.charAt(0);
                putValue(MNEMONIC_KEY, mnemonic);
            }
    
            public void actionPerformed(ActionEvent e) {
                progressBar.setValue(0);
                setEnabled(false);
                MyWorker myWorker = new MyWorker();
                myWorker.addPropertyChangeListener(new WorkerListener(this));
                myWorker.execute();
            }
        }
    
        private class WorkerListener implements PropertyChangeListener {
            private Action action;
    
            public WorkerListener(Action myAction) {
                this.action = myAction;
            }
    
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("progress".equals(evt.getPropertyName())) {
                    int progress = (int) evt.getNewValue();
                    progressBar.setValue(progress);
                } else if ("state".equals(evt.getPropertyName())) {
                    if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                        action.setEnabled(true);
    
                        @SuppressWarnings("rawtypes")
                        SwingWorker worker = (SwingWorker) evt.getSource();
                        try {
                            // always want to call get to trap and act on 
                            // any exceptions that the worker might cause
                            // do this even though get returns nothing
                            worker.get();
                        } catch (InterruptedException | ExecutionException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        private class MyWorker extends SwingWorker<Void, Void> {
            private static final int MULTIPLIER = 80;
            private int counter = 0;
            private Random random = new Random();
    
            @Override
            protected Void doInBackground() throws Exception {
                while (counter < 100) {
                    int increment = random.nextInt(10);
                    Thread.sleep(increment * MULTIPLIER);
                    counter += increment;
                    counter = Math.min(counter, 100);
                    setProgress(counter);
                }
                return null;
            }
        }
    }