我有一个JRuby脚本,它打开一个Java对话框来报告脚本的进度。
我正在捕获一个窗口关闭事件,希望对话框等待JRuby脚本中的一些清理完成,然后进行处理。相反,当用户按下右上角的红色x按钮时,对话框将挂起。
如何正确调用wait方法来等待标志更改?我是否正确使用了锁定对象?
jruby脚本调用此对话框
如果用户按下右上角的红色X,对话框将捕获窗口关闭事件并设置"已取消"标志。脚本会关注这个标志,然后开始关闭一些长时间运行的任务。完成后,它会更新对话框上的标志,表示已进行清理。与此同时,对话框正在循环,等待该标志更改。然后它调用dispose()。
我试过利用睡眠。出于某种原因,这扰乱了我的JRuby和对话框之间的一些东西,可以进行清理,但对话框不会处理。
使用wait,with synchronize(this)会生成一个IllegalMonitorException,但脚本会清理干净,对话框会正确处理异常。
看过很多其他关于如何同步等待方法的帖子,我很想了解这一点。
非常感谢您的帮助。
对话框类如下:
import javax.swing.*;
import java.awt.*;
public class MyDialog extends JDialog {
private boolean userCancelled;
private boolean scriptCleanedUp;
//private static Object lock = new Object();
public MyDialog(lock) {
userCancelled = false;
scriptCleanedUp = false;
setDefaultCloseOperation(2);
//[..] add various controls to dialog
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
// the jruby script keeps an eye on this flag to see if the user has cancelled the dialog
userCancelled = true;
/* once cancelled, wait for script to flag that it has performed its cleanup
*/
/* here is the problem area, what do I need to synchronize to use the wait method?
*/
while (!scriptCleanedUp) {
try {
synchronized (lock) {
lock.wait(1000000000);
}
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
dispose();
}
});
super.paint(super.getGraphics());
}
public boolean user_cancelled() { return userCancelled; }
public void setScriptCleanedUpToTrue() { this.scriptCleanedUp = true; }
public static void forBlock(MyDialogBlockInterface block)
{
MyDialog dialog = new MyDialog(new Object());
dialog.setVisible(true);
block.DoWork(dialog);
dialog.dispose();
}
}
如果有帮助的话,这就是JRuby脚本调用对话框的方式
MyDialog.forBlock do |dialog|
#do long running jruby task here
end
该代码有很多问题,包括:
- 您正在对Swing事件线程进行长时间调用,这将占用这个关键线程,从而保证冻结您的GUI
- 您直接呼叫
paint(...)
,这几乎是不应该做的事情
我敢打赌,如果您在后台线程(如SwingWorker)中进行长时间调用,而不是试图等待释放锁,则可以使用回调机制通知对话框关闭,只需确保您的对话框是模式JDialog就可以调用大部分问题。
例如:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
/**
* http://stackoverflow.com/a/29933423/522444
* @author Pete
*
*/
@SuppressWarnings("serial")
public class TestMyDialog2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
public TestMyDialog2() {
add(new JButton(new MyDialogAction("Please press this button!", this)));
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
// let's make this reasonably big
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
TestMyDialog2 mainPanel = new TestMyDialog2();
JFrame frame = new JFrame("TestMyDialog2");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
@SuppressWarnings("serial")
class MyDialogAction extends AbstractAction {
private JDialog dialog;
private MyWorker myWorker;
private TestMyDialog2 testMyDialog2;
public MyDialogAction(String name, TestMyDialog2 testMyDialog2) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
this.testMyDialog2 = testMyDialog2;
}
public void dialogIsClosing(WindowEvent e) {
if (myWorker != null && !myWorker.isDone()) {
myWorker.setKeepRunning(false);
} else {
if (dialog != null && dialog.isVisible()) {
dialog.dispose();
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
Window mainGui = SwingUtilities.getWindowAncestor(testMyDialog2);
dialog = new JDialog(mainGui, "My Dialog", ModalityType.APPLICATION_MODAL);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.add(Box.createRigidArea(new Dimension(200, 100)));
dialog.addWindowListener(new DialogWindowListener(this));
dialog.pack();
myWorker = new MyWorker();
myWorker.addPropertyChangeListener(new MyWorkerListener(dialog));
myWorker.execute();
dialog.setLocationRelativeTo(mainGui);
dialog.setVisible(true);
}
}
class MyWorker extends SwingWorker<Void, Void> {
private volatile AtomicBoolean keepRunning = new AtomicBoolean(true);
@Override
protected Void doInBackground() throws Exception {
// to emulate long-running code
while (keepRunning.get()) {
Thread.sleep(200);
System.out.println("Long running background code is running");
}
System.out.println("Doing shut-down process. Will close in 10 seconds");
for (int i = 0; i < 10; i++) {
System.out.println("Countdown: " + (10 - i));
Thread.sleep(1000); // emulate a long running shut-down process
}
return null;
}
public void setKeepRunning(boolean newValue) {
this.keepRunning.getAndSet(newValue);
}
}
class MyWorkerListener implements PropertyChangeListener {
private JDialog dialog;
public MyWorkerListener(JDialog dialog) {
this.dialog = dialog;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
dialog.dispose();
try {
((MyWorker) evt.getSource()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
class DialogWindowListener extends WindowAdapter {
private MyDialogAction myDialogAction;
public DialogWindowListener(MyDialogAction myDialogAction) {
this.myDialogAction = myDialogAction;
}
@Override
public void windowClosing(WindowEvent e) {
myDialogAction.dialogIsClosing(e);
}
}