0

i'm new to this community!

I want to ask you something about SwingWorker and its relationship with a GUI.

I know there are some answered questions about SwingWorker, and i've already read a lot of them, taking some helpful advices.

Now i'd like to post some code i wrote for a basic application which counts the number of files and folders from a specified directory.

Since the search could take a lot of time, i want a progress bar to be displayed during the process. Also, i would like users to have the possibility of stopping count process by clicking a button or simply closing the frame which contains the progress bar.

Here there are some questions on the code posted below:

  • The call to execute() method for SwingWorker is the last instruction of WaitingFrame constructor: is there a better place for it?
  • The dispose() method for WaitingFrame is called from SwingWorker's done() method, is it correct? If a count process is very fast, could the dispose method be called before the waiting frame is actually visible? As a result, i would have two open frames...
  • Is there a better method to interrupt the process and to manage the message dialogs shown to users? I used two boolean variables, valid and interrupted, to achieve my purpose...

And here's the code:

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import javax.swing.*;
import javax.swing.border.*;
public class CountFiles
{
    public static void main(String[] args)throws Exception
    {
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                try
                {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    new CountFilesFrame().setVisible(true);
                }
                catch(Exception ex){
                    ex.printStackTrace();
                }
            }
        });
    }
}
class CountFilesFrame extends JFrame
{
    private JTextField field;
    public CountFilesFrame()
    {
        super("Conta File e Cartelle");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        JPanel pane=(JPanel)getContentPane();
        pane.setBackground(Color.WHITE);
        pane.setBorder(new EmptyBorder(5,20,5,20));
        JPanel center=new StyledPanel(pane,BorderLayout.CENTER,new FlowLayout(FlowLayout.LEFT,5,10)),bottom=new StyledPanel(pane,BorderLayout.SOUTH,new FlowLayout(FlowLayout.LEFT,20,0));
        // Center panel
        center.add(new JLabel("Cartella :"));
        String text="";
        try{
            File folder=new File("../");
            text=folder.exists()?folder.getCanonicalPath():"";
        }
        catch(Exception ex){}
        field=new JTextField(text,25);
        center.add(field);
        // JTextArea
        String newLine=System.getProperty("line.separator"),message="Scegliere la cartella da cui far partire la ricerca."+newLine+
        "Sara' contato il numero di file e di cartelle presenti "+newLine+"nella directory inserita e in tutte le sottocartelle";
        JTextArea area=new JTextArea(message);
        area.setEditable(false);
        area.setFont(field.getFont());
        pane.add(area,BorderLayout.NORTH);
        // Bottom panel
        bottom.add(new JButton(new AbstractAction("Cambia Cartella"){
            public void actionPerformed(ActionEvent e){
                changeDirectory();
            }
        }));
        bottom.add(new JButton(new AbstractAction("Inizia ricerca"){
            public void actionPerformed(ActionEvent e){
                new WaitingFrame(CountFilesFrame.this);
            }
        }));
        pack();
        setLocationRelativeTo(null);
    }
    public void changeDirectory()
    {
        JFileChooser chooser=new JFileChooser(field.getText());
        chooser.setDialogTitle("Cambia Cartella");
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        if(chooser.showDialog(this,"Scegli")==JFileChooser.APPROVE_OPTION)
        {
            try
            {
                File selected=chooser.getSelectedFile();
                if(selected.exists())field.setText(selected.getCanonicalPath());
            }
            catch(Exception ex){}
        }
    }
    private class WaitingFrame extends JFrame
    {
        private Counter counter;
        public WaitingFrame(CountFilesFrame f)
        {
            super("Ricerca File");
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            addWindowListener(new WindowAdapter(){
                public void windowClosing(WindowEvent e){
                    stopCounter();
                }
            });
            setResizable(false);
            JPanel pane=(JPanel)getContentPane(),buttonPanel=new StyledPanel(pane,BorderLayout.SOUTH,new FlowLayout(FlowLayout.CENTER,0,10));
            JLabel label=new JLabel("Conteggio in corso...",JLabel.CENTER);
            label.setBorder(new EmptyBorder(0,0,10,0));
            pane.add(label,BorderLayout.NORTH);
            pane.setBackground(Color.WHITE);
            pane.setBorder(new EmptyBorder(10,40,0,40));
            JProgressBar progressBar=new JProgressBar(0,100);
            progressBar.setBorderPainted(false);
            progressBar.setIndeterminate(true);
            pane.add(progressBar,BorderLayout.CENTER);
            buttonPanel.add(new JButton(new AbstractAction("Annulla"){
                public void actionPerformed(ActionEvent e){
                    stopCounter();
                }       
            }));
            while(pane.getSize().width!=pane.getPreferredSize().width)pack();
            setLocationRelativeTo(null);
            setVisible(true);
            (counter=new Counter()).execute();
        }
        public void stopCounter()
        {
            counter.interrupt();
            counter.cancel(true);
        }
        private class Counter extends SwingWorker<Void,Void>
        {
            private boolean valid=true,interrupted=false;
            private int filesNumber=0,foldersNumber=0;
            protected Void doInBackground()
            {
                File folder=new File(field.getText());
                if(!folder.exists()||!folder.isDirectory())valid=false;
                else countFiles(folder);
                return null;
            }
            protected void done()
            {
                dispose();
                if(interrupted)return;
                else if(!valid)JOptionPane.showMessageDialog(CountFilesFrame.this,"Inserire una cartella valida","Percorso specificato errato",JOptionPane.ERROR_MESSAGE);
                else JOptionPane.showMessageDialog(CountFilesFrame.this,"Sono stati trovati "+(foldersNumber-1)+" cartelle e "+filesNumber+" file","Ricerca completata",JOptionPane.INFORMATION_MESSAGE);
            }
            private void countFiles(File file)
            {
                if(file.isDirectory())
                {
                    foldersNumber++;
                    for(File nested:file.listFiles())countFiles(nested);            
                }
                else filesNumber++;
            }
            public void interrupt()
            {
                interrupted=true;
            }
        }
    }
}
class StyledPanel extends JPanel
{
    public StyledPanel(JPanel parent,String position,LayoutManager layout)
    {
        super(layout);
        setBackground(Color.WHITE);
        parent.add(this,position);
    }
}

I posted all the application code, so you can try to compile and run it.

Thanks in advance for your help!

PS: i didn't change interface language, i'm sorry for that. Also, i'm sorry for my bad english...

Ansharja
  • 1,237
  • 1
  • 14
  • 37
  • *"didn't change interface language"* It does not seem related to the problem, so why would you bother? I note all the class names and attributes are in English. That's handy. :) – Andrew Thompson Jun 10 '16 at 11:35

1 Answers1

1

The call to execute() method for SwingWorker is the last instruction of WaitingFrame constructor: is there a better place for it?

There is nothing wrong with how you are calling it.

The dispose() method for WaitingFrame is called from SwingWorker's done() method, is it correct? If a count process is very fast, could the dispose method be called before the waiting frame is actually visible? As a result, i would have two open frames...

It is correct and the situation you describe cannot happen. SwingWorker.done() is called on EDT via a delayed SwingUtilities.invokeLater call and you already called JFrame.setVisible(true) before constructing the SwingWorker (on EDT).

Instead of multiple frames, consider using a dialog, perhaps a modal one if you are attempting to block user input (like here).

Is there a better method to interrupt the process and to manage the message dialogs shown to users? I used two boolean variables, valid and interrupted, to achieve my purpose...

There certainly is a better way of doing this, considering that your current code is dangerously close to not being thread safe. Consider using an AtomicBoolean instead of boolean if both EDT and swing worker need to access those two members.

You should also check the interrupted flag somewhere and exit your file listing loop upon it changing.

You can also wrap any swing code into a SwingUtilities.invokeLater call anywhere inside SwingWorker.doInBackground() for fine grained GUI updates. Doing this essentially has the same effect as if done() had been invoked but you control when and how many times it is called. Check here for a code example. Your own code does this to pass execution from the main thread to EDT inside your main() method (the reason for this code pattern is that all swing code must execute on EDT).

Community
  • 1
  • 1
predi
  • 5,528
  • 32
  • 60
  • First of all, thank you so much for your suggestions! – Ansharja Jun 14 '16 at 06:15
  • First of all, thank you so much for your suggestions! I'll try to apply what you said for the last point. Regarding dialogs, i have considered the use of a modal dialog, but the call to setVisible wouldn't block process start? should i call execute() before setVisible() in that case ? I thought it would have been even more risky for what i asked in my second point... – Ansharja Jun 14 '16 at 06:23
  • A modal dialog does block user input after `setVisible(true)` - the call does not return until `setVisible(false)`, but it does not block EDT (events are still processed). You will have to call `execute()` prior to `setVisible()`, or have `doInBackground()` call it for you. Take a look at the linked chicken/egg question. It should address your doubts. – predi Jun 14 '16 at 06:50
  • Note that the second bullet will no longer apply for a modal dialog. It works for `JFrame`, since there is no blocking. – predi Jun 14 '16 at 06:52
  • Perfect! Thank you !! :) – Ansharja Jun 14 '16 at 08:02
  • Don't forget to [accept](http://stackoverflow.com/tour) this answer, if it answers your question. – predi Jun 14 '16 at 10:10
  • done :) i 've already tried to vote it up, but my reputation is still too low :( – Ansharja Jun 14 '16 at 10:16