0

When I start my application it opens a JFrame (the main window) and a JFilechooser to select an input directory, which is then scanned.

The scan method itself creates a new JFrame which contains a JButton and a JProgressBar and starts a new Thread which scans the selected Directory. Up until this point everything works fine.

Now I change the Directory Path in my Main Window, which calls the scan method again. This time it creates another JFrame which should contain the JProgressBar and the JButton but it shows up empty (The JFrame Title is still set).

update: minimal example

public class MainWindow
{
    private JFrame      _frame;
    private JTextArea   _textArea;
    private ProgressBar _progress;

    public MainWindow() throws InterruptedException, ExecutionException
    {
        _frame = new JFrame("Main Window");
        _textArea = new JTextArea();

        _frame.add(_textArea);
        _frame.setSize(200, 200);
        _frame.setVisible(true);

        _textArea.setText(doStuffinBackground());

        _progress.dispose();
    }

    private String doStuffinBackground() throws InterruptedException,
            ExecutionException
    {
        setUpProgressBar();
        ScanWorker scanWorker = new ScanWorker();
        scanWorker.execute();

        return scanWorker.get();
    }

    private void setUpProgressBar()
    {
        // Display progress bar
        _progress = new ProgressBar();
    }

    class ProgressBar extends JFrame
    {
        public ProgressBar()
        {
            super();

            JProgressBar progressBar = new JProgressBar();
            progressBar.setIndeterminate(true);
            progressBar.setStringPainted(false);

            add(progressBar);

            setTitle("Progress Window");
            setSize(200, 200);
            toFront();
            setVisible(true);
        }
    }

    class ScanWorker extends SwingWorker<String, Void>
    {
        @Override
        public String doInBackground() throws InterruptedException
        {
            int j = 0;
            for (int i = 0; i < 10; i++)
            {
                Thread.sleep(1000);
                j += 1;
            }
            return String.valueOf(j);
        }
    }

    public static void main(String[] args) throws InvocationTargetException,
            InterruptedException
    {
        SwingUtilities.invokeAndWait(new Runnable()
        {
            public void run()
            {
                // Start the main controller
                try
                {
                    new MainWindow();
                }
                catch (InterruptedException | ExecutionException e) {}
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • 1) See [The Use of Multiple JFrames, Good/Bad Practice?](http://stackoverflow.com/q/9554636/418556) 2) For better help sooner, post an [MCVE](http://stackoverflow.com/help/mcve) (Minimal, Complete, Verifiable Example). 3) What is your question? – Andrew Thompson Aug 20 '14 at 01:08
  • @andrew-thompson Actually i wanted to use a JDialog because it would be cleaner to make it modal instead of disabling the Main JFrame and reenabling it afterwards, but from what i found online it would complicate handling the cancel button because the listener code would need to be completely in the JDialog class itself (my teacher doesn't really like that because it would violate the MVC Pattern (atleast in his opinion) – Peter Hugelberg Aug 20 '14 at 02:58

1 Answers1

2

From the basic looks of your scan method, you are blocking the Event Dispatching Thread, when you scan the directory, which is preventing it from updating the UI.

Specifically, you don't seem to truly understand what Callable and FutureTask are actually used for or how to use them properly...

Calling FutureTask#run will call the Callable's call method...from within the current thread context.

Take a look at Concurrency in Swing for more details...

Instead of trying to use FutureTask and Callable in this manner, consider using a SwingWorker, which is designed to do this kind of work (and uses Callable and FutureTask internally)

Have a look at Worker Threads and SwingWorker for more details

Now, before you jump down my throat and tell me that "it works the first time I ran it", that's because you're not starting your UI properly. All Swing UI's should be create and manipulated from within the context of the Event Dispatching Thread. You main method is executed in, what is commonly called, the "main thread", which is not the same as the EDT. This is basically setting up fluke situation in where the first time you call scan, you are not running within the context of the EDT, allowing it to work ... and breaking the single thread rules of Swing in the process...

Take a look at Initial Threads for more details...

I would also consider using a JDialog instead of another frame, even if it's not modal, it makes for a better paradigm for your application, as it really should only have a single main frame.

Updated based on new code

So, basically, return scanWorker.get(); is a blocking call. It will wait until the doInBackground method completes, which means it's block the EDT, still...'

Instead, you should be making use of the publish, process and/or done methods of the SwingWorker

Worker

import java.util.ArrayList;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class MainWindow {

    private JFrame _frame;
    private JTextArea _textArea;
    private ProgressBar _progress;

    public MainWindow() {
        _frame = new JFrame("Main Window");
        _textArea = new JTextArea();

        _frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        _frame.add(new JScrollPane(_textArea));
        _frame.setSize(200, 200);;
        _frame.setVisible(true);

        doStuffinBackground();
    }

    private void doStuffinBackground() {
//        _progress = new ProgressBar();
//        ScanWorker scanWorker = new ScanWorker();
//        scanWorker.execute();
//        return scanWorker.get();

        _progress = new ProgressBar();
        ScanWorker worker = new ScanWorker(_textArea, _progress);
        worker.execute();
        _progress.setVisible(true);
    }

    class ProgressBar extends JDialog {

        public ProgressBar() {
            super(_frame, "Scanning", true);

            JProgressBar progressBar = new JProgressBar();
            progressBar.setIndeterminate(true);
            progressBar.setStringPainted(false);

            add(progressBar);

            setTitle("Progress Window");
            pack();
            setLocationRelativeTo(_frame);
        }
    }

    class ScanWorker extends SwingWorker<List<String>, String> {

        private JTextArea textArea;
        private ProgressBar progressBar;

        protected ScanWorker(JTextArea _textArea, ProgressBar _progress) {
            this.textArea = _textArea;
            this.progressBar = _progress;

        }

        @Override
        protected void process(List<String> chunks) {
            for (String value : chunks) {
                textArea.append(value + "\n");
            }
        }

        @Override
        public List<String> doInBackground() throws Exception {
            System.out.println("...");
            int j = 0;
            List<String> results = new ArrayList<>(25);
            for (int i = 0; i < 10; i++) {
                Thread.sleep(1000);
                j += 1;
                System.out.println(j);
                results.add(Integer.toString(j));
                publish(Integer.toString(j));
            }
            return results;
        }

        @Override
        protected void done() {
            progressBar.dispose();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MainWindow();
            }
        });
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • but scanWorker.get() shouldn't block the Progressbar getting added to the JFrame right? It doesnt matter if the application blocks while the background task is running (the user would have to wait for it to finish anyway to do anything) – Peter Hugelberg Aug 20 '14 at 04:12
  • ``SwingWorker#get` is a block method, it blocks the current thread until the `doInBackground` method has completed. Because this is called from within the EDT, it's blocking the EDT and preventing it from performing any updates... – MadProgrammer Aug 20 '14 at 04:13
  • 1
    I think you need to take a look at [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/) – MadProgrammer Aug 20 '14 at 04:14
  • Yeah i think so too... but first i'll get some sleep (7 in the morning here), also marked your Answer as correct, now i just need to apply that to my application, thanks for not giving up on me ;) – Peter Hugelberg Aug 20 '14 at 05:15