2

I have a customized JFileChooser

Its approveSelection() method is slightly modified:

public void approveSelection()
{
    File file = getSelectedFile();

    changeGui();

    final Object a = makeALongCalcualtion(file);

    if (a != null)
    {
        super.approveSelection();

        SwingWorker<Document, Void> open = new SwingWorker<Document, Void>()
        {
            @Override
            protected Document doInBackground() throws Exception
            {
                return createADocument(a);
            }

            @Override
            protected void done()
            {
                try
                {
                    if(get() != null)
                    {
                        changeGui();
                    }

                    else
                    {
                        //TODO error message
                        changeGui();                        
                    }
                }

                catch (InterruptedException | ExecutionException e)
                {                   
                    //TODO error message
                    changeGui();
                }           
            }
        };

        open.execute();
    }

    else
    {
        //TODO error message
        changeGui();
    }
}

The changeGui() method sets a JProgressBar to indeterminate and updates a JLabel with a new string.

If file provided to makeALongCalcualtion(file) is of invalid type, it will return null, otherwise it returns info that is passed to SwingWorker which can use it to acutally create the representation of a file in the program (the Document object).

However, this doesn't work as it should because makeALongCalcualtion(file) isn't called within SwingWorker method, and that blocks the EDT.

In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it.

So the bottom line is, how do I call super.approveSelection() from within doInBackground() for this specific case?

//More info

What is supposed to happen:

  1. User selects and opens a file
  2. JLabel and JProgressBar are updated, indeterminate progress starts to play.
  3. If makeALongCalcualtion(file) return null user is warned with an error window, but the JFileChooser stys open, making it possible to choose again when the error window is closed.
  4. Otherwise, super.approveSelection() is called, allowing the chooser to close.
  5. A document is created (but the method that creates the document return null if something goes wrong).
  6. If everything is fine, JLabel updates and progressBar animation is stopped (indeterminate is set to false).
  7. If something goes wrong same thing happens as in step 6, only with different message in JLabel.

What happens:

  1. same
  2. same
  3. same, but when makeALongCalculation(file); begins, progressBar freezes.
  4. same
  5. same
  6. same, but the animation isn't stopped (since the progressbar is frozen), only the frozen "picture" is removed and progressbar returns to it's previous state.
  7. same

EDIT

I have made some alterations to my program and I now have this:

approveSelection():

public void approveSelection()
{
    File file = getSelectedFile();

    Main.getStatusBar().startOpen();

    final WorkOpen open = new WorkOpen(file);

    open.execute();

    open.addPropertyChangeListener(new PropertyChangeListener()
    {
        @Override
        public  void propertyChange(PropertyChangeEvent evt) {
            if ("state".equals(evt.getPropertyName())) {
                if (evt.getNewValue().equals("DONE"))
                {
                    if (open.failed())
                    {
                        //TODO error message                        
                        Main.getStatusBar().endOpen(false);
                    }

                    else
                    {
                        Main.getStatusBar().endOpen(true);
                    }
                }
            }
        }
    });
}

SwingWorker:

class WorkOpen extends SwingWorker<Document, Void>
{
boolean failed = false;
File file;

public boolean failed()
{
    return failed;
}

@Override
protected Document doInBackground() throws Exception
{
    ArrayList<String> data = Opener.extractData(file);

    if (data != null)
    {
        //My little path/name/similar managing system
        FileComplex fullPath = new FileComplex(file.toString());

        return Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data); 
    }

    else
    {
        failed = true;
        return null;
    }
}

@Override
protected void done()
{
    try
    {
        if(get() != null)
        {
            Main.addDocument(get());
        }
    }

    catch (InterruptedException | ExecutionException e)
    {
        failed = true;
    }           
}

WorkOpen(File file)
{
    this.file = file;
}
}

The problem now is where to call super.approveSelection(). It has to wait for the worker to finish executing, yet I can't call it from the property change listener.

What to do here?

EDIT 2

As HovercraftFullOfEels suggested, I fixed my code and it compiled and ran. But the problem of JProgressBar freezeing remained. Also, I had to introduce something I don't know if I should have:

private void superApproveSelection()
    {
        super.approveSelection();
    }

    public void approveSelection()
    {
        final File file = getSelectedFile();

        class OpenWorker extends SwingWorker<Boolean, Void>
        {
            Document document;

            Document getDocument()
            {
                return document;
            }

            @Override
            protected Boolean doInBackground() throws Exception
            {
                ArrayList<String> data = Opener.extractData(file);

                if (data != null)
                {
                    //I had to start the progressBar here, because if invalid
                    //file was selected (extractData(file) returns null if it was),
                    //nothing should happen (maybe an error
                    //window later, handled with a new Runnable() same as this:
                    SwingUtilities.invokeLater(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            Main.getStatusBar().startOpen();            
                        }               
                    });

                    FileComplex fullPath = new FileComplex(file.toString());

                    document = Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data); 

                    return true;
                }

                else
                {
                    return false;
                }
            }
        };

        final OpenWorker opener = new OpenWorker();

        opener.addPropertyChangeListener(new PropertyChangeListener()
        {
            @Override
            public  void propertyChange(PropertyChangeEvent evt)
            {
                if ("state".equals(evt.getPropertyName()))
                {
                    if (evt.getNewValue() == SwingWorker.StateValue.DONE)
                    {
                        if(opener.getDocument() != null)
                        {
                            superApproveSelection();
                            Main.addDocument(opener.getDocument());
                            Main.getStatusBar().endOpen(true);
                        }

                        else
                        {
                            try
                            {
                                //I'm retrieveing doInBackground()'s value to see if
                                //progressBar needs stoping (it also displays some
                                //text, so it must not be called unless the
                                //progressBar was started).
                                if (opener.get())
                                {
                                    Main.getStatusBar().endOpen(false);
                                }
                            }

                            catch (InterruptedException | ExecutionException e) 
                            {
                                //TODO error something went wrong
                            }
                        }
                    }
                }
            }
        });

        opener.execute();
    }
Karlovsky120
  • 6,212
  • 8
  • 41
  • 94
  • You will want to post a small self-contained compilable runnable program that shows your problem, an [sscce](http://sscce.org), similar to what I've posted below. It's the best way for us to tease out the subtleties of your problem. – Hovercraft Full Of Eels Mar 30 '13 at 15:54
  • I found out what the problem is. You see, after SwingWorker is done, the document has to be painted on the screen, and then AFTER the JProgressBar would be stopped. But the painting of the document blocked the EDT and that is my problem. Since achiving what I'm trying to would be multithreading in Swing, there is no possible way I could do this, right? – Karlovsky120 Mar 30 '13 at 16:01
  • What do you mean by the "painting of the document"? – Hovercraft Full Of Eels Mar 30 '13 at 16:04
  • createDocument() is executed within doInBackground and that creates a JPanel with a bunch of JPanels on it displaying various text in various JLabels, etc. When it's all done and the program checked that the document is valid, it gets added to a JPanel in masterWindow which then also validates and repaints that JPanel (that is the addDocument() method). – Karlovsky120 Mar 30 '13 at 16:11
  • BTW, addDocument() also does some painting that can't be moved outside of it, so I'll then just let the application freeze for that half a second (it's always half a second, regardelss to file size. Thnaks for all your help. – Karlovsky120 Mar 30 '13 at 16:19
  • Painting often can be done off of the EDT, but creating components should be done on the EDT. – Hovercraft Full Of Eels Mar 30 '13 at 16:33
  • Really? I thought it was the other way around... But there is no way I could simultaneously create components and make JProgressBar play its animation? – Karlovsky120 Mar 30 '13 at 16:35
  • I believe that all basic GUI construction should be done on the event thread. As for your progress bar, I think that I'd need an [sscce](http://sscce.org) to be able to comment intelligently as to what possible problems might be. – Hovercraft Full Of Eels Mar 30 '13 at 17:05
  • in the end it all breaks down to creating GUI components and playing the JProgressBar animation at the same time... – Karlovsky120 Mar 30 '13 at 17:11

1 Answers1

2

"In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it."

No, not true at all. super.approveSelection() would not have to be called inside of the SwingWorker.

Why not simply create a SwingWorker, add a PropertyChangeListener to it, and when the SwingWorker's state is done, call super.approveSelection() if indicated?

OK, here is my example below. Explanation to follow:

import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;

import javax.swing.*;
import javax.swing.text.*;

@SuppressWarnings("serial")
public class ApproveSelectionTest extends JPanel {
   private JTextArea textArea = new JTextArea(30, 60);

   public ApproveSelectionTest() {
      textArea.setEditable(false);
      textArea.setFocusable(false);

      JPanel btnPanel = new JPanel();
      btnPanel.add(new JButton(new MyGetFileAction("Get Text File Text")));

      setLayout(new BorderLayout());
      add(new JScrollPane(textArea), BorderLayout.CENTER);
      add(btnPanel, BorderLayout.PAGE_END);
   }

   private class MyGetFileAction extends AbstractAction {
      public MyGetFileAction(String text) {
         super(text);
      }

      public void actionPerformed(java.awt.event.ActionEvent arg0) {
         MyFileChooser myFileChooser = new MyFileChooser();
         int result = myFileChooser.showOpenDialog(ApproveSelectionTest.this);
         if (result == JFileChooser.APPROVE_OPTION) {
            Document doc = myFileChooser.getDocument();
            textArea.setDocument(doc);
         }
      };
   }

   private static void createAndShowGui() {
      ApproveSelectionTest mainPanel = new ApproveSelectionTest();

      JFrame frame = new JFrame("ApproveSelectionTest");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

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

@SuppressWarnings("serial")
class MyFileChooser extends JFileChooser {
   private WorkOpen workOpen = null;
   private JDialog progressDialog = null;

   public MyFileChooser() {
   }

   @Override
   public void approveSelection() {
      JProgressBar progBar = new JProgressBar();
      progBar.setIndeterminate(true);
      Window win = SwingUtilities.getWindowAncestor(this);
      progressDialog = new JDialog(win, "Checking File", ModalityType.APPLICATION_MODAL);
      progressDialog.getContentPane().add(progBar);
      progressDialog.pack();
      progressDialog.setLocationRelativeTo(null);


      File file = getSelectedFile();

      workOpen = new WorkOpen(file);

      workOpen.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent pcEvt) {
            if (SwingWorker.StateValue.DONE == pcEvt.getNewValue()) {
               if (progressDialog != null) {
                  progressDialog.dispose();
               }

               try {
                  boolean bool = workOpen.get().booleanValue();
                  if (bool) {
                     superApproveSelection();
                  } else {
                     JOptionPane.showMessageDialog(MyFileChooser.this, "Invalid File Chosen");
                  }
               } catch (InterruptedException e) {
                  e.printStackTrace();
               } catch (ExecutionException e) {
                  e.printStackTrace();
               }
            }
         }
      });

      workOpen.execute();
      progressDialog.setVisible(true);

   }

   // ****** this is key *****
   private void superApproveSelection() {
      super.approveSelection();
   }

   public Document getDocument() {
      if (workOpen == null) {
         return null;
      } else {
         return workOpen.getDocument();
      }
   }
}

class WorkOpen extends SwingWorker<Boolean, Void> {
   private static final long SLEEP_TIME = 4 * 1000;
   private Document document = null;
   private File file = null;

   public WorkOpen(File file) {
      this.file  = file;
   }

   @Override
   protected Boolean doInBackground() throws Exception {
      if (file == null || !file.exists()) {
         return Boolean.FALSE;
      }
      Thread.sleep(SLEEP_TIME);
      String fileName = file.getName();
      if (fileName.contains(".txt")) {
         Scanner scan = new Scanner(file);
         StringBuilder stringBuilder = new StringBuilder();
         while (scan.hasNextLine()) {
            stringBuilder.append(scan.nextLine() + "\n");
         }

         document = new PlainDocument();
         document.insertString(0, stringBuilder.toString(), null);
         return Boolean.TRUE;
      }
      return Boolean.FALSE;
   }

   public Document getDocument() {
      return document;
   }

}

Explanation and key points from my example:

  • This example behaves very simply. You choose a file, and if the file exists and contains ".txt" in its name, then it reads in the document and displays the text in a JTextField.
  • Else it displays a warning message but leaves the JFileChooser displayed.
  • Probably the key point: I've given my MyFileChooser class a private superApproveSelection() method that can be called by my PropertyChangeListener. This exposes the super's approveSelection() method to the inner class, one of the problems you were having.
  • The order of calling code is important in my approveSelection() override.
  • In this method I first create my JProgressBar and its dialog but don't yet display it immediately. It really doesn't have to be created first, but it sure needs to be displayed last.
  • I create my SwingWorker, workOpen, but don't yet execute it.
  • I add my PropertyChangeListener to the SwingWorker before executing the SwingWorker.
  • I then execute my SwingWorker
  • I then display my modal JDialog with the indeterminate JProgressBar.
  • My SwingWorker is structured so that its doInBackground returns a Boolean, not a Document.
  • I have it create a (very simple) Document if all works out OK that holds the content of the text file, and set a private "doc" field obtainable by a getter method, and then have doInBackground return Boolean.TRUE if all works well.
  • I've given my doInBackground a Thread.sleep(4000) just to pretend that its action takes a lot of time. Yours of course won't have this.
  • In the PropertyChangeListener if the SwingWorker is DONE, I'll dispose of the progress bar dialog and then call get() on the SW to get the Boolean result.
  • If it's Boolean.TRUE, then call the superApproveSelection() method described above.
  • Else show an error message. Note that since the super's approveSelection() isn't called, the file chooser dialog remains displayed.
  • If the approveSelection is called then the code that displays my file chooser dialog will get the appropriate return value, will extract the Document from the file chooser and displays the Document in a JTextArea.
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • I have tried using your method, but I got stuck. See the question edit for mor details. – Karlovsky120 Mar 29 '13 at 23:41
  • @Karlovsky120: runnable example posted. Explanation to follow. – Hovercraft Full Of Eels Mar 30 '13 at 01:12
  • I don't have time to check it out now completley (will do later today), but I think the private class for calling super.approveselection() is exactly what I need. I did thought of it, but I didn't think it would ever work so I never tried it... – Karlovsky120 Mar 30 '13 at 11:19
  • I really appreciate your help! – Karlovsky120 Mar 30 '13 at 11:20
  • It helped, and though everything should work fine, I may have missed something somewhere, since it still freezes... – Karlovsky120 Mar 30 '13 at 15:28
  • @Karlovsky120: then yes, you've missed something somewhere, since my code above demonstrates proof of concept. Time to do some debugging. Use a debugger or println statements to find out exactly where in your program run the GUI is freezing, and then use that information to find out why the Swing event thread is tied up at that time. – Hovercraft Full Of Eels Mar 30 '13 at 15:30
  • @mKorbel: he's trying to change the behavior of JFileChooser so that the JFileChooser dialog is displayed, then after a file is selected, keep the file chooser dialog displayed while his program performs a long-running process to check the validity of the file. If the file is valid, close the JFileChooser (via `super.approveSelection()`), or if not, show an error message and keep the file chooser dialog shown. – Hovercraft Full Of Eels Mar 30 '13 at 15:51
  • - [have to read](http://stackoverflow.com/questions/7053865/cant-get-arrayindexoutofboundsexception-from-future-and-swingworker-if-threa), - notice better could be to split that to the two separate thread, 1. one for whatever upto code line returned possible exception, 2. if success then load buffered content to the GUI – mKorbel Mar 30 '13 at 16:03