Я использую SwingWorker? - PullRequest
0 голосов
/ 09 июля 2020

Пока мой код отправляет HTTP-вызовы, я хочу, чтобы он изменил JLabel в окне (в основном показывая, сколько времени осталось до завершения HTTP-вызовов). Я искал SwingWorkers из-за другого вопроса, который я здесь задал, но я не уверен, как я его использую. Код, который я пишу, в основном имеет al oop для отправки вызовов, каждый раз рассчитывая время, необходимое для выполнения вызова, вычисляет приблизительное оставшееся время и затем отправляет его на JLabel (NB, JLabel находится в другом

Большинство SwingWorker примеров показывают, что функция продолжается в фоновом режиме, на которую не влияет рабочий поток (например, счетчик полностью основан на времени, а не изменяется кодом). Если это так, то не является ли изменение JLabel просто частью рабочего потока, поскольку код проходит через новый l oop -> вычислить время и сделать вызов -> изменить JLabel? Я, вероятно, ошибаюсь, но тогда как мне изменить JLabel кодом, а не независимым потоком? *.

Вот мой код:

package transcription.windows;

import javax.swing.*;

import java.awt.*;

import static javax.swing.JFrame.EXIT_ON_CLOSE;

public class PleaseWaitWindow {

    private JLabel pleaseWaitJLabel = new JLabel("Please wait");
    private GridBagConstraints containerGbc = new GridBagConstraints();
    private Container contentPaneContainer = new Container();
    private JFrame pleaseWaitJFrame;

    public JLabel getPleaseWaitJLabel() {
        return pleaseWaitJLabel;
    }

    public JFrame setPleaseWaitWindow() {
        pleaseWaitJFrame = new JFrame();
        contentPaneContainer = setContentPane();
        pleaseWaitJFrame.setContentPane(contentPaneContainer);
        pleaseWaitJFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        pleaseWaitJFrame.setTitle("");
        pleaseWaitJFrame.setSize(350, 150);
        pleaseWaitJFrame.setLocationRelativeTo(null);
        pleaseWaitJFrame.setVisible(true);

        return pleaseWaitJFrame;
    }

    private Container setContentPane() {
        containerGbc.insets.bottom = 1;
        containerGbc.insets.top = 2;
        containerGbc.insets.right = 1;
        containerGbc.insets.left = 1;
        containerGbc.weightx = 1;
        containerGbc.weighty = 1;

        contentPaneContainer.setLayout(new GridBagLayout());
        contentPaneContainer.setSize(800, 700);
        setPleaseWaitJLabel();

        return contentPaneContainer;
    }

    private void setPleaseWaitJLabel() {
        containerGbc.gridx = 2;
        containerGbc.gridy = 5;
        containerGbc.gridwidth = 2;
        containerGbc.gridheight = 1;
        contentPaneContainer.add(pleaseWaitJLabel, containerGbc);
    }

    public void setJLabelDisplay(String displayTime) {
        pleaseWaitJLabel.setText(displayTime);
    }

    public void closeWindow() {
        pleaseWaitJFrame.dispose();
    }
    }

Метод, который является частью класса ServiceUpload:

    public String cuttingLoop(String mpBase64Piece, String jobName, String email) {
        Integer numberOfPiecesMinusEnd = (int) Math.ceil(mpBase64Piece.length() / 500000.0);
        List<String> base64List = new ArrayList<>();

        for (int i = 0; i < numberOfPiecesMinusEnd; i++) {

            if (mpBase64Piece.length() >= 500000) {
                base64List.add(mpBase64Piece.substring(0, 500000));
                mpBase64Piece = mpBase64Piece.substring(500000);
            }

        }
        base64List.add(mpBase64Piece);

        pleaseWaitWindow = new PleaseWaitWindow();
        pleaseWaitWindow.setPleaseWaitWindow();
        for (int n = 0; n < base64List.size(); n++) {
            numberOfLoopsLeft = numberOfPiecesMinusEnd - n;
            Stopwatch stopwatch = null;
            String tag;
            Stopwatch.createStarted();
            if (base64List.get(n) != null) {
                if (n == 0) {
                    tag = "start";
                } else if (n == base64List.size() - 1) {
                    tag = "end";
                } else {
                    tag = "middle";
                }
                stopwatch = Stopwatch.createStarted();
                response = providerUpload.executeUploadHttp(base64List.get(n), jobName, tag, email);
                stopwatch.stop();
            }
            long oneLoopTime = stopwatch.elapsed(TimeUnit.SECONDS);
            pleaseWaitWindow.setJLabelDisplay(numberOfLoopsLeft*oneLoopTime+" seconds remaining");
            LOGGER.info("complete");
        }
        pleaseWaitWindow.closeWindow();
        return response;
    }

Одна из моих проблем заключалась в том, что код не отображался 'JLabel', когда 'SwingWorker' не используется с приведенным выше кодом.

1 Ответ

1 голос
/ 09 июля 2020

Лучше всего разделить код на зоны ответственности. Пусть go с тремя: 1. рабочий (ie загрузка); 2. дисплей (ie обновление JLabel); 3. интеграция двух (первые два независимы друг от друга, поэтому вам понадобится что-то, чтобы ie их вместе).

Абстрагируясь от реальной работы, вы можете использовать стандартные интерфейсы. Первый - это просто Runnable, ie, не принимающий никаких параметров и ничего не возвращающий. Второй - Consumer<String>, потому что он принимает String (для отображения), но ничего не возвращает. Третий будет вашим основным элементом управления.

Давайте начнем с элемента управления, потому что это просто:

Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);

Это запустит рабочий процесс в отдельном потоке, который звучит так, как вы хотите.

Итак, вот ваш загрузчик:

Consumer<String> display = // tbd, see below
Runnable worker = () -> {
    String[] progress = {"start", "middle", "finish"};
    for (String pr : progress) {
        display.accept(pr);
        Thread.sleep(1000); // insert your code here
    }
}

Обратите внимание, что этот воркер действительно зависит от потребителя; это несколько "нечисто", но подойдет.

Теперь о дисплее. Определив его как Consumer<String>, он достаточно абстрактен, чтобы мы могли просто распечатать прогресс на консоли.

Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);

Однако вы хотите обновить JLabel; так что потребитель будет выглядеть как

Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);

Ваш фактический вопрос

Итак, если вы это сделаете, вы заметите, что текст вашего ярлыка не обновляется, когда вы ожидать. Это потому, что label.setText(s) выполняется в потоке, в котором выполняется рабочий; его нужно вставить в резьбу Swing. Вот где появляется SwingWorker.

В SwingWorker есть поле progress, которое вы можете использовать для своих меток; у него также есть doInBackground(), который является вашим фактическим рабочим потоком загрузки. Итак, вы получаете

class UploadSwingWorker {
    public void doInBackground() {
        for(int i = 0; i < 3; i++) {
            setProgress(i);
            Thread.sleep(1000); // again, your upload code
        }
    }
}

Так как это обновляет ваш лейбл? setProgress поднимает PropertyChangeEvent, который вы можете перехватить; это делается с помощью PropertyChangeListener с подписью

void propertyChange(PropertyChangeEvent e)

Это функциональный интерфейс, поэтому вы можете реализовать это с помощью лямбда, в вашем случае

String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
    int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
    String value = displays[index];
    label.setText(value);
}

, и вы можете добавить его в SwingWorker, используя

SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker

Simpler

Обратите внимание, что я никогда не использовал SwingWorker таким образом. Гораздо более простой способ обойти проблему, заключающуюся в том, что GUI не обновляется изнутри вашего рабочего потока, - это вызвать обновление GUI, используя SwingUtilities.invokeLater().

Возвращение к исходному Consumer<String> I поднял, вы можете сделать

    Consumer<String> display = s -> SwingUtilities.invokeLater(
        () -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
    );

, и это должно сработать. Это позволяет вам держать вашего рабочего в более абстрактном Runnable и использовать обычные механизмы планирования для его запуска (ExecutorService.submit() или CompletableFuture.runAsync(), например), при этом позволяя обновлять GUI на столь же простом уровне.

...