Отображение неопределенного JProgressBar во время выполнения пакетного файла

Я некоторое время просматривал SO и google для ответа на этот вопрос, но я не могу найти тот, который действительно работает. Начну с самого начала:

Я создал класс Java с методом, который запускает пакетный файл в фоновом режиме (окно команд не появляется). Программа отлично работает, за исключением того, что это может немного сбить с толку конечного пользователя, поскольку для завершения пакетного файла требуется некоторое время - пользователь не будет знать, запущена программа или нет. После завершения выполнения пакетного сценария появляется диалоговое окно с сообщением о завершении, но в течение периода времени между запуском метода и появлением диалогового окна программа выглядит так, как будто программа ничего не делает.

Итак, вот мой вопрос: я бы очень хотел отобразить новый кадр с текстовой областью, которая показывает вывод пакетного файла. Однако я понимаю, что это очень сложно сделать без создания временных файлов, записи в них, чтения из них и так далее. Я бы предпочел избежать этого, если это возможно. Поэтому я решил, что может быть лучше отображать неопределенный JProgressBar во время выполнения процесса и закрывать его, когда процесс завершен. К сожалению, я не думаю, что Swing справится с этим, поскольку для этого потребуется одновременно запустить несколько процессов. Я слышал о SwingWorker, но не совсем уверен, как его использовать в данном случае. У меня есть следующий SSCCE, который работает, но не имеет индикатора выполнения.

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!");
    }
}

Пока p.waitFor() ожидает процесса, на экране ничего нет. Я просто хочу, чтобы что-то показывало пользователю, что процесс все еще выполняется. Мысли? Спасибо!


person DerStrom8    schedule 15.12.2013    source источник


Ответы (1)


Вы можете запустить ProcessBuilder в фоновом режиме SwingWorker, как показано ниже, чтобы получить оба вывод и индикатор выполнения.

изображение

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 http://stackoverflow.com/a/20603012/230513
 * @see http://stackoverflow.com/a/17763395/230513
 */
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();
            }
        });
    }
}
person trashgod    schedule 16.12.2013
comment
Это фантастика! Мне придется немного поработать с ним, чтобы он соответствовал моим потребностям. Это должен быть отдельный класс и передавать ему мои команды процесса. В настоящее время у меня есть переменная класса и метод мутатора для установки значения. По какой-то причине пакетный файл не запускается. Я сделаю еще немного отладки и вернусь к вам. - person DerStrom8; 16.12.2013
comment
Это работает, как и ожидалось, большое спасибо! К сожалению, он работает очень медленно. Я не думаю, что есть способ, которым это может быть ускорено? - person DerStrom8; 16.12.2013
comment
Я предполагаю, что это командный файл; Мне пришлось замедлить его, чтобы проверить; профиль, чтобы быть уверенным. - person trashgod; 16.12.2013
comment
Перед добавлением этого класса пакетный файл запускается и завершается примерно через 30 секунд. Сейчас это продолжается уже больше часа (может быть, два?) и до сих пор не закончено. - person DerStrom8; 16.12.2013
comment
Трудно сказать: партия ожидает ввода? слишком много текста? без разрывов строк? это работает с dir /s /b? - person trashgod; 16.12.2013
comment
Извините, я должен был упомянуть, что я использую свой собственный пакетный файл, который я написал для копирования файлов с одного диска на другой. Построитель процессов использует массив строк с именем commands, который включает в себя все аргументы и команды, используемые для вызова/запуска bat. Я до сих пор не уверен, почему это занимает так много времени, и я даже не знаю, с чего начать поиск. - person DerStrom8; 16.12.2013
comment
Теперь я не совсем знаком с классом SwingWorker, но я читал, что запуск внешнего процесса из EDT приведет к тому, что он будет очень вялым или даже перестанет отвечать. Кажется, это прекрасно описывает мою проблему. Я не думаю, что это то, что происходит здесь? - person DerStrom8; 18.12.2013
comment
Верно; идея состоит в том, чтобы запустить медленный процесс в doInBackground, фоновом потоке рабочего процесса и process результатах в EDT. Ваша партия заканчивается, хотя и медленно, или она остается заблокированной на неопределенный срок? - person trashgod; 18.12.2013
comment
В конечном итоге он завершается, но процесс, который занимает 30 секунд, обычно занимает более двух часов при запуске с использованием класса SwingWorker. Я внес в него несколько правок, но только для изменения макета пользовательского интерфейса. Это не должно было вызвать каких-либо серьезных ошибок, как это - person DerStrom8; 18.12.2013
comment
Выдает ли ваша партия достаточно вывода, чтобы заглушить текстовую область или вызвать подкачку? Что показало профилирование или диспетчер задач? Я не могу воспроизвести описанный вами эффект, но вы можете попробовать скопировать файлы, используя один из подходов, показанных здесь. - person trashgod; 18.12.2013
comment
Мой пакетный файл состоит в основном из цикла, содержащего команду xcopy. Каталоги, которые я копирую, передаются с использованием массива команд, который находится в моем коде, который я разместил в исходном сообщении. Вывод пакетного файла — это просто строка, отображающая копируемый каталог. Я не уверен, забивает ли это текстовую область или нет. Диспетчер задач показывает только процесс java и процесс cmd (связанный с этим проектом). Я не совсем уверен, как сделать профилирование, которое вы предлагаете, если честно, это новый термин для меня. Хотя я проверю вашу ссылку. Большое спасибо за вашу помощь, кстати! - person DerStrom8; 18.12.2013
comment
Вы видите результат каждого xcopy? Во время работы приложения запустите jvisualvm и подключитесь к нему, чтобы посмотреть, что происходит. См. также этот ответ на xcopy или попробуйте Files.copy. - person trashgod; 18.12.2013
comment
Я вижу вывод xcopy, да. Я не запускаю свои программы в командной строке, я обычно использую Eclipse. Есть ли инструмент, который я могу использовать в Eclipse вместо этого? Если я не смогу заставить это работать, мне, возможно, придется переосмыслить поток всей моей программы и использовать Files.copy вместо пакетных файлов Windows. - person DerStrom8; 18.12.2013
comment
Я ненавижу расширять эту ветку комментариев, я просто хотел упомянуть, что в настоящее время копирование 2 МБ файлов занимает 2-3 минуты. Запуск прямо из командного файла занимает около 3-5 секунд. - person DerStrom8; 18.12.2013
comment
С тех пор, как я последний раз комментировал, я заметил, что при нажатии кнопки остановки процесс команды не закрывается. Он остается открытым в диспетчере задач. Я предположил, что это то, что p.destroy() должен был сделать - закрыть его, но, похоже, он этого не делает. Есть идеи? - person DerStrom8; 21.12.2013
comment
Это ожидаемо; ОС хоста владеет потоком, который должен находиться в состоянии припаркованного/ожидающего; диспетчер задач — довольно грубое представление; профиль, чтобы увидеть более подробную информацию. - person trashgod; 21.12.2013
comment
Значит, после завершения процессы должны оставаться открытыми? Это похоже на нехватку ресурсов, и мой компьютер несколько раз чуть не зависал из-за зависания проводника Windows. - person DerStrom8; 21.12.2013
comment
Я вижу 48 байтов на несуществующего рабочего, которые исчезают при выходе из JVM. - person trashgod; 21.12.2013
comment
Странный. Мой продолжал работать даже после выхода из JVM, и в какой-то момент он даже продолжал копировать файлы, даже когда была нажата кнопка остановки. Я полагаю, что я буду продолжать возиться с этим и посмотреть, что я могу понять. Большое спасибо, вы очень помогли - person DerStrom8; 21.12.2013