我需要阻止用户在第一次点击仍然执行时对JButton进行多次点击。
我能够为这个问题找到解决方案,但我并不完全理解为什么它有效。
Bellow我发布了有效的代码(修剪到最小)和无法使用的代码。
在第一个例子中(好的)如果你运行它并多次单击该按钮,则只考虑一个动作和第二个例子(坏)如果你多次单击鼠标就会至少执行两次动作。
第二个(坏)示例根本不使用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和@BorisPavlović提供的信息后,我创建的代码看起来相当不错。
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
答案 0 :(得分:8)
你有两个选择
1)JButton#setMultiClickThreshhold
2)你必须将这个想法分解为actionListener或Action
中的两个独立动作javax.swing.Action
(来自javax.swing.Timer
),SwingWorker
或Runnable#Thread
答案 1 :(得分:2)
好的,这是使用Action
的代码段代码:
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);
答案 2 :(得分:1)
还有两种方式。
您可以定义标志。在动作开始时设置它,在结束后重置。检查actionPerformed
中的标志。如果inProgress==true
什么也不做。
另一种方法是删除侦听器并在操作结束后将其分配回来。
答案 3 :(得分:1)
正确的方法是使用SwingWorker。当用户在将作业提交到SwingWorker
之前单击按钮时,按钮的状态应更改为禁用JButton#setEnabled(false)
。在SwingWorker
完成后,应将按钮的作业状态重置为启用状态。这是Oracle的tutorial on SwingWorker
答案 4 :(得分:1)
经过多年处理这个问题的挫败感,我已经实施了一个我认为最好的解决方案。
首先,为什么没有其他工作:
JButton::setMutliclickThreshold()
并非真正的最佳解决方案,因为(正如您所说)无法知道设置阈值的时间。这样做只是为了防止双击快乐的最终用户,因为你必须设置一个任意的阈值。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()
方法中,否则类不会工作即可。
无论如何,我认为这是一个很好的解决方案,不应该重构代码来重新编写你的应用程序。
对此解决方案的反馈非常感兴趣。
答案 5 :(得分:0)
请注意,当您在GUI中修改任何内容时,如果您在另一个线程中,则必须使用invokeLater或invokeAndWait在Event Dispatch线程上运行代码。因此,当您尝试从另一个线程修改启用状态时,第二个示例是不正确的,并且它可能导致不可预测的错误。