我有一个在基于Java 1.5 Swing的应用程序中实现的功能。 如果在处理AWTEvent时发生特殊异常,我必须弹出一个替代表单,解决问题并继续处理原始事件。 当我将事件重新发送到组件时,没有任何反应。当我将事件推入事件队列时,没有任何事情发生。我假设事件中有一些状态字段将其标记为已处理,因此组件不会将其拾取。 到目前为止,我找不到重新创建事件克隆的方法。自定义事件在这里没有帮助,因为我希望上一个事件得到处理。
在swing应用程序中,现有的事件队列被内部队列替换。
private class ApplicationEventQueue extends EventQueue
{
private final ArrayList listeners=new ArrayList();
protected void initialize()
{
Toolkit.getDefaultToolkit().getSystemEventQueue().push(this);
}
.
.
.
}
作为dispatch事件的一部分,该类拦截调用ans委托给超类。如果发生异常,它将弹出一个消息框,其中包含“抱歉为此不便”的消息。
@Override
protected void dispatchEvent(AWTEvent event)
{
try
{
super.dispatchEvent(event);
if (peekEvent() != null && userEventDispatched)
{
raiseIdleEvent();
userEventDispatched = false;
}
else
{
int eventId = event.getID();
if (eventId == KeyEvent.KEY_TYPED || eventId == MouseEvent.MOUSE_CLICKED)
{
userEventDispatched = true;
}
}
}
catch (Throwable ex)
{
onError(ex);
}
}
所需的功能是能够超时用户会话。会话超时时,服务器将抛出特定异常。超时时,将提示用户重新登录,并且将中止原始操作。 我想要做的是,作为onError的一部分,我将通过显示一个表单来处理异常。该特定事件将被消耗,但在重新认证之后,我可以将相同的事件重新发送到应用程序,或者可能将其推送到事件队列中。 这两种方法都失败了,因为我认为事件中有标志表明它是否被发布和消费。
抱歉糟糕的格式化,我还没想出标记。 这里的任何帮助将不胜感激。
固定:谢谢你的所有答案。修复(我自己找不到)是在调用时捕获会话超时异常。应用程序弹出一个对话框,要求用户重新进行身份验证。身份验证成功后,对话框已关闭。这让我感到惊讶。
我不确定,但似乎事件在显示对话框时仍然卡在队列中,一旦对话框关闭,它就会被传递给控制器。无论如何。
答案 0 :(得分:2)
我不会尝试从事件的角度来解决这个问题。事件系统无意以这种方式工作。
我将定义一个封装与服务器交互的接口(X)。实现Y将保存最后一个服务器请求的参数。发生超时后,用户重新进行了身份验证,然后我可以要求Y重新发送最后一个请求。
额外的好处:由于X是一个接口,这简化了测试,因为可以用模拟对象替换Y来测试GUI,测试代码可以在没有GUI的情况下调用Y.
<强>更新强>
这是一种使用SwingWorker在bg线程上运行服务器交互的方法。 ChangeEvent用于将结果返回给EDT进行处理。 SwingWorker的发布/过程用于处理用户交互以进行重新验证。 SwingWorker的一个好处是,如果服务器需要很长时间来响应,UI仍然可以响应重绘事件。
class Test extends JPanel {
private JButton b;
public Test() {
b = new JButton(new AbstractAction("Do something") {
@Override
public void actionPerformed(ActionEvent e) {
final JButton btn = (JButton) e.getSource();
btn.setEnabled(false);
Object req = new Object(); // Replace w/ apropriate type
new RequestDispatch(req, new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
final Object req = e.getSource();
// Do something with data from 'req'
btn.setEnabled(true);
}
});
}
});
add(b);
}
}
class RequestDispatch extends SwingWorker<Object, Void> {
private enum DispatchState { Ready, Running, Canceled }
private final ChangeListener listener;
private DispatchState dstate = DispatchState.Ready;
private final Object req;
RequestDispatch(Object req, ChangeListener listener)
{
this.req = req;
this.listener = listener;
execute();
}
@Override
protected Object doInBackground() throws Exception {
while (true) {
DispatchState st = getDState();
if (st == DispatchState.Ready)
{
try {
setDstate(DispatchState.Running);
// send request to the server, update req with response
return req;
}
catch (TimeoutException ex) {
this.publish((Void)null);
synchronized (this) {
wait();
}
}
}
if (st == DispatchState.Canceled) {
return req;
}
}
}
@Override
protected void process(List<Void> chunks) {
// Session has timed out
// Ask the user to re-authenticate
int result = JOptionPane.showConfirmDialog(null, "Login");
if (result == JOptionPane.CANCEL_OPTION) {
setDstate(DispatchState.Canceled);
}
else {
setDstate(DispatchState.Ready);
}
}
private synchronized DispatchState getDState() {
return dstate;
}
private synchronized void setDstate(DispatchState dstate) {
this.dstate = dstate;
notifyAll();
}
}
答案 1 :(得分:1)
您可以将原始事件放入自定义包装器类,其名称为AWTEventWrapper
,其扩展名为AWTEvent
。然后,该类覆盖所有方法并将它们委托给包装事件,但consume()
和isConsumed()
除外(保留默认实现),以便您可以假设事件未被消耗,并允许它再次被处理。
答案 2 :(得分:0)
查看JDK6代码时,AWTEvent.consumed
不能设置为false,因为只有consume()
,没有unconsume()
。
我认为OP自己的#5是唯一的方式,虽然确实很危险。 (我实际上刚学会了如何从AWTEvent#get_InputEvent_CanAccessSystemClipboard()
设置私有字段。)
在onError()
中,您可以创建另一个覆盖getNextEvent
的事件队列加上一个布尔标志,这样当您调用EventQueue#push()
时,新队列会将所有当前事件保留在列表中,然后执行错误通知,最后弹出新队列,在发生异常的情况下翻转consumed
,重新发送它,然后是您在列表中保存的早期事件。