简体   繁体   English

进度条的Java Swing Worker - UI长时间没有响应

[英]Java Swing Worker for Progress Bar - UI remains unresponsive for a long time

I developed an application on Windows using Java, Swing (window builder). 我使用Java,Swing(窗口构建器)在Windows上开发了一个应用程序。
On a button click, my application will go to another class ( FileManager.java file) to count the total number of files in the input folder (meanwhile progressBar will be in indeterminate mode). 单击按钮,我的应用程序将转到另一个类( FileManager.java文件)来计算输入文件夹中的文件总数(同时progressBar将处于不确定模式)。 Once the number of files are known, progressBar maximum value is set. 知道文件数后,设置progressBar最大值。

Then I call convertToXLS(fileMgr) to read through the content of each file (1 kb) and update the progressBar as each file is read. 然后我调用convertToXLS(fileMgr)来读取每个文件的内容(1 kb)并在读取每个文件时更新progressBar

Here is the code for it: 这是它的代码:

public class xmlToXL {
            public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                xmlToXL window = new xmlToXL();
                window.frame.setVisible(true);
            }
        });
        private void initialize() {
            ...... some UI code ........
        btnConvertXmlTo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {    
                try {
                    preConvertToXLS();
                    Task task = new Task(folderPath.getText());
                    task.execute();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }// end of actionPerformed method
        }); // end of action listened

}//end of initialize

    public void preConvertToXLS() {    //method to set few UI properties
        btnConvertXmlTo.setEnabled(false);
        progressBar.setVisible(true);
        progressBar.setStringPainted(true);
        progressBar.setIndeterminate(true);
        progressBar.setString("Calculating Total number of files...");
        progressBar.setForeground(new Color(0, 102, 0));
    }

    ParserUtils parUtils = new ParserUtils(); //class to parse XML files (in another .java file)

    private void convertToXLS(FileManager fileMgr) {
        try {
            int i=1;
            parUtils.reset();
            progressBar.setValue(0);
            List<File> files = fileMgr.getFiles();
            for(File file : files) {
                progressBar.setString("Reading " + i+ " of " + fileMgr.getSize()+ " files");
                parUtils.parseFileUsingDOM(file); // This will read content of the input file 
                progressBar.setValue(i++);
            }
            btnConvertXmlTo.setEnabled(true);


        } catch (Exception e) {

        } 
    }

    class Task extends SwingWorker<Void, Void> {
        private FileManager fileMgr;

        public Task(String srcPath) {
            this.fileMgr = new FileManager(new File(srcPath));

        }

        /*
         * Main task. Executed in background thread.
         */
        @Override
        public Void doInBackground() {
            try {
                progressBar.setIndeterminate(true);
                fileMgr.readFiles();
                progressBar.setIndeterminate(false);
                progressBar.setMaximum(fileMgr.getSize());
                convertToXLS(fileMgr);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        /*
         * Executed in event dispatching thread
         */
        @Override
        public void done() {
            Toolkit.getDefaultToolkit().beep();
            try {
            progressBar.setString("FileRead Successful");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }//end of task class
}//end of My class

My UI becomes unresponsive after fileMgr.readFiles(); fileMgr.readFiles();之后,我的UI变得没有响应fileMgr.readFiles(); . It takes a minute or two, sometimes three and then executes convertToXLS(fileMgr) . 它需要一两分钟,有时三分钟,然后执行convertToXLS(fileMgr)

FileManager.java

import XMLParsing.DetermineEncoding;

public class FileManager {

    public HashMap<String, ArrayList<String>> dirFiles = null;
    public ArrayList<String> dirNames = null;
    public int numberOfFiles;
    private File src;
    private List<File> files;

    public FileManager(File src) {
        this.src = src;
        dirNames = new ArrayList<String>();
        dirFiles = new HashMap<String, ArrayList<String>>();
        numberOfFiles = 0;
        files = new ArrayList<File>();
    }



    public int getSize() {
        return numberOfFiles;
    }

    public ArrayList<String> getDirectories(){
        return dirNames;
    }

    public List<File> getFiles() {
        Iterator it = dirFiles.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry) it.next();
            String folderName = (pair.getKey()).toString();
            ArrayList<String> FileNames = (ArrayList<String>) pair.getValue();
            if (FileNames != null) {
                for (String fileName : FileNames) {
                    if(replaceSelected(fileName)) {
                        File fXmlFile = new File(fileName);
                        files.add(fXmlFile);
                    }
                    else {
                    }
                }
            }
        }
        return files;
    }

    public void readFiles() throws IOException {
        readFiles(src);
    }

    private void readFiles(File folder) throws IOException {
        if (folder.isDirectory()) {
            ArrayList<String> fileNames = new ArrayList<String>();
            for (final File file : folder.listFiles()) {
                if (file.isDirectory()) {
                    readFiles(file);
                } else {
                    String fileName = (file.getPath()).toString();
                    if(fileName.toLowerCase().endsWith(".xml")) {
                        fileNames.add(file.getPath());
                        numberOfFiles = numberOfFiles + 1;
                        System.out.println(".");
                        if(!dirNames.contains(file.getParentFile().getName()))
                                dirNames.add(file.getParentFile().getName());
                    }
                }
            }
            dirFiles.put(folder.getName(), fileNames);
        }
    }

    private boolean replaceSelected(String filePath) {
        String line;
        String input = "";
        try {
            DetermineEncoding DE = new DetermineEncoding();
            String encoding = DE.getFileEncoding(filePath);
            InputStreamReader file = new InputStreamReader(new FileInputStream(
                    filePath), encoding);
            BufferedReader br = new BufferedReader(file);
            while ((line = br.readLine()) != null) {
                input += line.toString() + " ";
            }
            file.close();
            Writer out = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(filePath), "UTF-8"));
            out.append(input.trim());
            out.flush();
            out.close();
        } catch (Exception e) {
            return false;
        }
        return true;
    }

}

DetermineEncoding.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.mozilla.universalchardet.UniversalDetector;

public class DetermineEncoding {

    public DetermineEncoding() {
        // TODO Auto-generated constructor stub
    }

    public String getFileEncoding(String fileName) throws IOException {
        byte[] buf = new byte[4096];
        java.io.FileInputStream fis = new FileInputStream(fileName);
        UniversalDetector detector = new UniversalDetector(null);
        int nread;
        while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
          detector.handleData(buf, 0, nread);
        }
        detector.dataEnd();
        String encoding = detector.getDetectedCharset();
        if (encoding != null) {
          return encoding;
        } else {
          return "";
        }


    }

}

Please help me identify the issue. 请帮我确定一下这个问题。

The basic problem is one of perception. 基本问题是感知问题。 You "think" that the UI isn't responding, when in fact, it's just waiting. 您“认为”UI没有响应,事实上,它只是在等待。

When you call readFiles , it goes through ALL the files you have previously scanned for, reads them and then writes them out again, all while the progress bar is in "determinate" mode, so it doesn't display anything. 当你调用readFiles ,它会遍历你之前扫描过的所有文件,读取它们然后再将它们写出来,同时进度条处于“确定”模式,因此它不会显示任何内容。

What you need is some way for the FileManager to provide updates about it's progress back to your worker, but the worker goes through a number of other methods, which also have to provide progress notifications. 您需要的是FileManager提供有关它返回给您的工作进程的更新的一些方法,但是工作人员会通过许多其他方法,这些方法也必须提供进度通知。

This would seem to hint at the need for some kind of Observer Pattern , where by the worker can be informed by other parts of the program when something has changed. 这似乎暗示了对某种观察者模式的需求 ,当某些事情发生变化时,工作者的其他部分可以通知工人。

We also need to do all of this in a way which allows use to update the UI safely 我们还需要以允许使用安全地更新UI的方式完成所有这些操作

Let's start with the observer... 让我们从观察者开始......

public interface ProgressListener {
    public void progressChanged(double progress);
    public void setStatus(String text);
}

Pretty simple, it will notify you when the progress of status changes, allowing who ever is listening to make updates as they see fit. 非常简单,它会在状态变化进行时通知您,允许谁在听取他们认为合适的更新。

The basic progress value is between 0-1, meaning that the listener doesn't actually care how many values you have, it only cares about your progress, this eliminates the need to try and update the progress bar about it's maximum value and instead, simply focus on the need to update the progress bar between 0-100 基本进度值介于0-1之间,这意味着监听器实际上并不关心您拥有多少值,它只关心您的进度,这样就无需尝试更新进度条关于它的最大值,而是只需关注0-100之间更新进度条的需要

Now we need to make room for it in the rest of the API 现在我们需要在API的其余部分为它腾出空间

private void convertToXLS(FileManager fileMgr, ProgressListener listener) {
    try {
        int i = 1;
        listener.progressChanged(0d);
        List<File> files = fileMgr.getFiles(listener);
        for (File file : files) {
            listener.setStatus("Reading " + i + " of " + fileMgr.getSize() + " files");
            parUtils.parseFileUsingDOM(file); // This will read content of the input file 
            listener.progressChanged(i / (double) files.size());
        }
        btnConvertXmlTo.setEnabled(true);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

And FileManager#getFiles .... FileManager#getFiles ....

public List<File> getFiles(ProgressListener listener) {
    Iterator it = dirFiles.entrySet().iterator();
    int count = dirFiles.size();
    for (Map.Entry<String, ArrayList<String>> entry : dirFiles.entrySet()){
        count += entry.getValue() == null ? 0 : entry.getValue().size();
    }
    int index = 0;
    listener.setStatus("Processing files...");
    while (it.hasNext()) {
        Map.Entry pair = (Map.Entry) it.next();
        String folderName = (pair.getKey()).toString();
        ArrayList<String> FileNames = (ArrayList<String>) pair.getValue();
        if (FileNames != null) {
            for (String fileName : FileNames) {
                if (replaceSelected(fileName)) {
                    File fXmlFile = new File(fileName);
                    files.add(fXmlFile);
                } else {
                }
                index++;
                listener.progressChanged(index / (double)count);
            }
        }
    }
    return files;
}

Next, we need to update the Task to take advantage of it's progress support, we also need to allow for the ability to change the status of the progress bar. 接下来,我们需要更新Task以利用它的进度支持,我们还需要允许更改进度条的状态。

This we can do through the publish / process methods, to send messages from the background thread to the EDT. 我们可以通过publish / process方法,将消息从后台线程发送到EDT。 We can also "cheat" a little and use it to send messages to change the indeterminate state of the progress bar (fyi: You could also use the property change listener support to do this as well, which might be a cleaner approach) 我们也可以“欺骗”一点并使用它来发送消息来改变进度条的indeterminate状态(fyi:你也可以使用属性更改监听器支持来执行此操作,这可能是一种更干净的方法)

class Task extends SwingWorker<Void, String> {

    protected   static final String INDETERMINATE_ON = "indeterminate.on";
    protected   static final String INDETERMINATE_OFF = "indeterminate.off";

    private FileManager fileMgr;

    public Task(String srcPath) {
        this.fileMgr = new FileManager(new File(srcPath));

    }

    @Override
    protected void process(List<String> chunks) {
        for (String text : chunks) {
            if (INDETERMINATE_OFF.equals(text)) {
                progressBar.setIndeterminate(false);
            } else if (INDETERMINATE_ON.equals(text)) {
                progressBar.setIndeterminate(true);
            } else {
                progressBar.setString(text);
            }
        }
    }

    /*
         * Main task. Executed in background thread.
     */
    @Override
    public Void doInBackground() {
        try {
            publish(INDETERMINATE_ON);
            fileMgr.readFiles();
            publish(INDETERMINATE_OFF);
            convertToXLS(fileMgr, new ProgressListener() {
                @Override
                public void progressChanged(double progress) {
                    setProgress((int) (progress * 100d));
                }

                @Override
                public void setStatus(String text) {
                    publish(text);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
         * Executed in event dispatching thread
     */
    @Override
    public void done() {
        Toolkit.getDefaultToolkit().beep();
        try {
            progressBar.setString("FileRead Successful");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}//end of task class

And finally, we need to add a PropertyChangeListener to the Task when we create it, so we can get the progress updates and update the progress bar... 最后,我们需要在创建它时向Task添加一个PropertyChangeListener ,这样我们就可以获得progress更新并更新进度条......

task.addPropertyChangeListener(new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        String name = evt.getPropertyName();
        switch (name) {
            case "progress":
                int value = (int) evt.getNewValue();
                progressBar.setValue(value);
                break;
        }
    }
});

Simple :P 简单:P

The code seems fine. 代码似乎很好。 The only thing I cant seem to check is the FileManager. 我似乎无法检查的唯一事情是FileManager。 With a FileReader it runs in a separate thread allowing user operations at the same time. 使用FileReader,它在一个单独的线程中运行,允许用户同时操作。 So I guess the FileManager must be causing the problem. 所以我猜FileManager必然会导致问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM