Отключите JButton во время фоновой работы, чтобы избежать нескольких кликов - PullRequest
8 голосов
/ 08 декабря 2011

Мне нужно, чтобы пользователь не нажимал несколько раз на JButton, пока выполняется первый щелчок.

Мне удалось найти решение этой проблемы, но я не до конца понимаю, почему он работает.

Ниже я опубликовал код (обрезанный до минимума), который работает, и тот, который не работает.

В первом примере (хорошо), если вы запускаете его и нажимаете кнопку только несколько разодно действие рассматривается как для второго примера (плохо), если щелкнуть мышью несколько раз, вы получите действие, выполненное по крайней мере дважды.

Во втором (плохо) примере просто не используется метод invokeLater ().

Откуда возникает разница в поведении?

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

public class TestButtonTask {

    public static void main(String[] args) {

        final JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        final JButton task = new JButton("Test");

        task.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                long t = System.currentTimeMillis();
                System.out.println("Action received");

                task.setText("Working...");
                task.setEnabled(false);

                SwingUtilities.invokeLater(new Thread() {

                    @Override
                    public void run() {
                        try {
                            sleep(2 * 1000);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
                        }

                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {
                                task.setEnabled(true);
                                task.setText("Test");
                            }
                        });

                    }
                });
            }
        });

        frame.add(task);
        frame.pack();
        frame.setVisible(true);
    } //end main
} //end class

А теперь «неправильный» код

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

public class TestButtonTask {

    public static void main(String[] args) {

        final JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        final JButton task = new JButton("Test");

        task.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                long t = System.currentTimeMillis();
                System.out.println("Action received");

                task.setText("Working...");
                task.setEnabled(false);

                SwingUtilities.invokeLater(new Thread() {

                    @Override
                    public void run() {
                        try {
                            sleep(2 * 1000);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
                        }

                        //SwingUtilities.invokeLater(new Runnable() {

                            //public void run() {
                                task.setEnabled(true);
                                task.setText("Test");
                            //}
                        //});

                    }
                });
            }
        });

        frame.add(task);
        frame.pack();
        frame.setVisible(true);
    } //end main
} //end class

После информации, предоставленной @kleopatra и @Boris Pavlović, вот код, который я создал и который работает довольно прилично.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

public class TestButtonTask {

    public static void main(String[] args) {

        final JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        final JButton task = new JButton("Test");

        task.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                task.setText("Working...");
                task.setEnabled(false);

                SwingWorker worker = new SwingWorker<Void, Void>() {

                    @Override
                    protected Void doInBackground() throws Exception {
                        try {
                            Thread.sleep(3 * 1000);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
                        }

                        return null;
                    }                    
                };

                worker.addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        System.out.println("Event " + evt + " name" + evt.getPropertyName() + " value " + evt.getNewValue());
                        if ("DONE".equals(evt.getNewValue().toString())) {
                            task.setEnabled(true);
                            task.setText("Test");
                        }
                    }
                });

                worker.execute();
            }
        });

        frame.add(task);
        frame.pack();
        frame.setVisible(true);
    } //end main
} //end class

Ответы [ 6 ]

8 голосов
/ 08 декабря 2011

у вас есть два варианта

1) JButton # setMultiClickThreshhold

2) вы должны разделить эту идею на два отдельных действия внутри actionListener или Action

  • первый. step, JButton # setEnabeld (false);
  • второй. шаг, затем вызовите оставшуюся часть кода, обернутую в javax.swing.Action (из и обработанный javax.swing.Timer), SwingWorker или Runnable#Thread
2 голосов
/ 08 декабря 2011

Хорошо, вот фрагмент кода, использующий действие

  • отключает себя при выполнении
  • он порождает задачу, в конце которой снова включается. Примечание: для простоты здесь задача моделируется с помощью таймера, в реальном мире будет создан SwingWorker для выполнения фоновой работы, прослушивания изменений его свойств и включения при получении выполненного
  • установить как действие кнопки

код:

    Action taskAction = new AbstractAction("Test") {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Action received ");
            setEnabled(false);
            putValue(NAME, "Working...");
            startTask();
        }

        // simulate starting a task - here we simply use a Timer
        // real-world code would spawn a SwingWorker
        private void startTask() {
            ActionListener l = new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    putValue(NAME, "Test");
                    setEnabled(true);

                }
            };
            Timer timer = new Timer(2000, l);
            timer.setRepeats(false);
            timer.start();
        }};

     JButton task = new JButton(taskAction);
1 голос
/ 17 мая 2014

После многих лет борьбы с разочарованием этой проблемой я реализовал решение, которое я считаю лучшим.

Во-первых, почему ничего не работает:

  1. JButton::setMutliclickThreshold() на самом деле не является оптимальным решением, потому что (как вы сказали) нет способа узнать, как долго установить порог.Это только хорошо для защиты от двойного щелчка счастливых конечных пользователей, потому что вы должны установить произвольный порог.
  2. JButton::setEnabled() - это явно хрупкое решение, которое только усложнит жизнь.

Итак, я создал SingletonSwingWorker.Синглтоны называются анти-паттернами, но при правильной реализации они могут быть очень мощными.Вот код:

public abstract class SingletonSwingWorker extends SwingWorker {

    abstract void initAndGo();

    private static HashMap<Class, SingletonSwingWorker> workers;
    public static void runWorker(SingletonSwingWorker newInstance) {
        if(workers == null) {
            workers = new HashMap<>();
        }
        if(!workers.containsKey(newInstance.getClass()) || workers.get(newInstance.getClass()).isDone()) {
            workers.put(newInstance.getClass(), newInstance);
            newInstance.initAndGo();
        }
    }
}

Это позволит вам создавать классы, которые расширяют SingletonSwingWorker и гарантируют, что только один экземпляр этого класса будет одновременно выполняться.Вот пример реализации:

public static void main(String[] args) {
    final JFrame frame = new JFrame();
    JButton button = new JButton("Click");
    button.setMultiClickThreshhold(5);
    button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            DisplayText_Task.runWorker(new DisplayText_Task(frame));
        }
    });

    JPanel panel = new JPanel();
    panel.add(button);
    frame.add(panel);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

static class DisplayText_Task extends SingletonSwingWorker {

    JFrame dialogOwner;
    public DisplayText_Task(JFrame dialogOwner) {
        this.dialogOwner = dialogOwner;
    }

    JDialog loadingDialog;
    @Override
    void initAndGo() {
        loadingDialog = new JDialog(dialogOwner);
        JProgressBar jpb = new JProgressBar();
        jpb.setIndeterminate(true);
        loadingDialog.add(jpb);
        loadingDialog.pack();
        loadingDialog.setVisible(true);
        execute(); // This must be put in the initAndGo() method or no-workie
    }

    @Override
    protected Object doInBackground() throws Exception {
        for(int i = 0; i < 100; i++) {
            System.out.println(i);
            Thread.sleep(200);
        }
        return null;
    }

    @Override
    protected void done() {
        if(!isCancelled()) {
            try {
                get();
            } catch (ExecutionException | InterruptedException e) {
                loadingDialog.dispose();
                e.printStackTrace();
                return;
            }
            loadingDialog.dispose();
        } else
            loadingDialog.dispose();
    }

}

В моих SwingWorker реализациях мне нравится загружать JProgressBar, поэтому я всегда делаю это перед запуском doInBackground().В этой реализации я загружаю JProgressBar в метод initAndGo() и также вызываю execute(), который необходимо поместить в метод initAndGo(), иначе класс не будет работать .

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

Очень заинтересованы в отзывах об этом решении.

1 голос
/ 08 декабря 2011

Правильный путь - использование SwingWorker .Когда пользователь нажимает кнопку перед отправкой задания на SwingWorker, состояние кнопки следует изменить на отключенное JButton#setEnabled(false).После завершения SwingWorker состояние работы кнопки должно быть сброшено.Вот учебник Oracle по SwingWorker

1 голос
/ 08 декабря 2011

Есть еще два способа.

Вы можете определить флаг. Установите его, когда действие начнется, и сбросьте после окончания. Проверьте флаги в actionPerformed. Если inProgress==true просто ничего не делать.

Другой способ - удалить слушателя и назначить его обратно после завершения действия.

0 голосов
/ 08 декабря 2011

Обратите внимание, что когда вы изменяете что-либо в графическом интерфейсе, ваш код должен запускаться в потоке Event Dispatch с использованием invokeLater или invokeAndWait, если вы находитесь в другом потоке. Поэтому второй пример неверен, так как вы пытаетесь изменить включенное состояние из другого потока, и это может привести к непредсказуемым ошибкам.

...