Display indeterminate JProgressBar while batch file runs
Asked Answered
I

1

5

I've been browsing SO and google for a while now for an answer to this question, but I can't seem to find one that really works. I'll start from the beginning:

I created a Java class with a method that runs a batch file in the background (the command window does not appear). The program works great, except that it would be a little confusing to the end user, since the batch file takes a while to complete--the user will not know if the program is still running or not. After the batch script finishes executing, a message dialog appears saying it's finished, but for the period of time between when the method is run and the dialog appears, it looks as if the program is doing nothing.

So here's my question: I would very much like to display a new frame with a text area that shows the output of the batch file. However, I understand that this is very difficult to do without creating temporary files, writing to them, reading from them, and so on. I would rather avoid that if possible. Therefore, I have decided it might be better to display an indeterminate JProgressBar while the process is running, and close it when the process is finished. Unfortunately, I don't think Swing can handle this since it would require running multiple processes at once. I have heard of a SwingWorker but am not exactly sure how that would be used in this case. I have the following SSCCE, which works, but does not have the progress bar implemented.

public myClass(){
    public static void main(String[] args){
        String[] commands = {"cmd.exe", "/C", "C:\\users\\....\\myBat.bat"};
        Process p = Runtime.getRuntime().exec(commands);
        p.waitFor()
        JOptionPane.showMessageDialog(null, "Process finished!");
    }
}

While p.waitFor() waits for the process, there is nothing on the screen. I just want something showing the user that a process is still running. Thoughts? Thanks!

Inheritor answered 15/12, 2013 at 22:33 Comment(0)
D
12

You can run a ProcessBuilder in the background of a SwingWorker, as shown below, to get both output and a progress bar.

image

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.swing.*;

/**
 * @se https://mcmap.net/q/456325/-display-indeterminate-jprogressbar-while-batch-file-runs
 * @see https://mcmap.net/q/456472/-swingworker-not-responding
 */
public class SwingWorkerExample {

    private final JLabel statusLabel = new JLabel("Status: ", JLabel.CENTER);
    private final JTextArea textArea = new JTextArea(20, 20);
    private JButton startButton = new JButton("Start");
    private JButton stopButton = new JButton("Stop");
    private JProgressBar bar = new JProgressBar();
    private BackgroundTask backgroundTask;
    private final ActionListener buttonActions = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent ae) {
            JButton source = (JButton) ae.getSource();
            if (source == startButton) {
                textArea.setText(null);
                startButton.setEnabled(false);
                stopButton.setEnabled(true);
                backgroundTask = new BackgroundTask();
                backgroundTask.execute();
                bar.setIndeterminate(true);
            } else if (source == stopButton) {
                backgroundTask.cancel(true);
                backgroundTask.done();
            }
        }
    };

    private void displayGUI() {
        JFrame frame = new JFrame("Swing Worker Example");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JPanel panel = new JPanel();
        panel.setBorder(
            BorderFactory.createEmptyBorder(5, 5, 5, 5));
        panel.setLayout(new BorderLayout(5, 5));

        JScrollPane sp = new JScrollPane();
        sp.setBorder(BorderFactory.createTitledBorder("Output: "));
        sp.setViewportView(textArea);

        startButton.addActionListener(buttonActions);
        stopButton.setEnabled(false);
        stopButton.addActionListener(buttonActions);
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(startButton);
        buttonPanel.add(stopButton);
        buttonPanel.add(bar);

        panel.add(statusLabel, BorderLayout.PAGE_START);
        panel.add(sp, BorderLayout.CENTER);
        panel.add(buttonPanel, BorderLayout.PAGE_END);

        frame.setContentPane(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private class BackgroundTask extends SwingWorker<Integer, String> {

        private int status;

        public BackgroundTask() {
            statusLabel.setText((this.getState()).toString());
        }

        @Override
        protected Integer doInBackground() {
            try {
                ProcessBuilder pb = new ProcessBuilder("ls", "-lR", "/");
                pb.redirectErrorStream(true);
                Process p = pb.start();
                String s;
                BufferedReader stdout = new BufferedReader(
                    new InputStreamReader(p.getInputStream()));
                while ((s = stdout.readLine()) != null && !isCancelled()) {
                    publish(s);
                }
                if (!isCancelled()) {
                    status = p.waitFor();
                }
                p.getInputStream().close();
                p.getOutputStream().close();
                p.getErrorStream().close();
                p.destroy();
            } catch (IOException | InterruptedException ex) {
                ex.printStackTrace(System.err);
            }
            return status;
        }

        @Override
        protected void process(java.util.List<String> messages) {
            statusLabel.setText((this.getState()).toString());
            for (String message : messages) {
                textArea.append(message + "\n");
            }
        }

        @Override
        protected void done() {
            statusLabel.setText((this.getState()).toString() + " " + status);
            stopButton.setEnabled(false);
            startButton.setEnabled(true);
            bar.setIndeterminate(false);
        }

    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new SwingWorkerExample().displayGUI();
            }
        });
    }
}
Dilate answered 16/12, 2013 at 3:33 Comment(19)
This is fantastic! I'll have to work with it a bit to make it fit my needs. It'll have to be a separate class, and pass my process commands in to it. I currently have a class variable and a mutator method to set the value. For some reason though it does not seem to run the batch file. I'll do a little more debugging and will get back to you.Inheritor
It's working as expected, thank you very much! Unfortunately it's running very slowly. I don't suppose there's a way it could be sped up?Inheritor
I'd guess it's the batch file; I had to slow it down to test; profile to be sure.Dilate
Before adding this class the batch file would start and finish within about 30 seconds. Now it's been going for over an hour (maybe two?) and still isn't finished.Inheritor
Hard to say: batch waiting for input? too much text? no line breaks? does it work with dir /s /b?Dilate
I'm sorry, I should have mentioned that I'm using my own batch file that I wrote to copy files from one drive to another. The process builder uses a string array called "commands", which involves all the arguments and commands that are used to call/run the bat. I'm still unsure why it takes so long though, and I wouldn't even know where to begin looking.Inheritor
Now, I'm not completely familiar with the SwingWorker class, but I have read that running an external process from the EDT will cause it to be very sluggish, or even become unresponsive. This seems to describe my problem perfectly. I don't suppose that's what is happening here?Inheritor
Right; the idea is to run the slow process in doInBackground, the worker's background thread, and process results on the EDT. Does your batch finish, albeit slowly, or does it remain blocked indefinitely?Dilate
It does finish eventually, but a process that takes 30 seconds normally took over two hours when run using the SwingWorker class. I did make a few edits to it, but that was only to change the UI layout. It shouldn't have caused any major bugs like thisInheritor
Does your batch produce enough output to choke the text area or cause swapping? What did profiling or task manager show? I can't reproduce the effect you describe, but you might try copying the files using one of the approaches shown here.Dilate
My batch file consists primarily of a loop containing the xcopy command. The directories I'm copying are passed in using the "commands" array that is in my code I posted in the original post. The output from the batch file is simply a line displaying the directory being copied. I'm not sure if this is choking up the text area or not. The task manager only shows a java process and a cmd process (related to this project). I'm not quite sure how to do the profiling you suggest, to be honest that's a new term for me. I'll check your link though. Many thanks for all your help, by the way!Inheritor
You're seeing the output from each xcopy? While your app is running, start jvisualvm and attach to it to see what's going on. See also this answer on xcopy or try Files.copy.Dilate
I do see the output from xcopy, yes. I do not run my programs in the command line, I generally use Eclipse. Is there a tool I can use in Eclipse instead? If I can't get this working I might have to rethink the flow of my entire program and use Files.copy instead of windows batch filesInheritor
I hate to extend this comment thread further, I just wanted to mention that it currently takes 2-3 minutes to copy 2MB of files. Running straight from the batch file it takes about 3-5 seconds.Inheritor
I have noticed since I last commented that when the stop button is pressed, it does not close the command process. It remains open in the task manager. I assumed that's what p.destroy() was supposed to do--close it--but it does not seem to be doing that. Any ideas?Inheritor
That's expected; the host OS owns the thread, which should be in the parked/waiting state; task manager is a rather coarse view; profile to see more detail.Dilate
So it's supposed to leave processes open after completing? That seems like a bit of a resource hog, and my computer has almost crashed a couple of times due to windows explorer freezing.Inheritor
I'm seeing 48 bytes per defunct worker, which disappear when the JVM exits.Dilate
Strange. Mine remained running even after the JVM was exited, and at one point it even continued to copy files even when the "stop" button was pressed. I suppose I'll keep mucking around with it and see what I can figure out. Thank a bunch, you've been a huge helpInheritor

© 2022 - 2024 — McMap. All rights reserved.