Karlovsky120
Karlovsky120

Reputation: 6352

Calling super.approveSelection() within a SwingWorker

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();
    }

Upvotes: 2

Views: 457

Answers (1)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285403

"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.

Upvotes: 2

Related Questions