如何在java中重新发送任何AWTEvent?

时间:2010-06-24 12:31:30

标签: java swing

我有一个在基于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的一部分,我将通过显示一个表单来处理异常。该特定事件将被消耗,但在重新认证之后,我可以将相同的事件重新发送到应用程序,或者可能将其推送到事件队列中。 这两种方法都失败了,因为我认为事件中有标志表明它是否被发布和消费。

  1. 即将发生的事件可能是任何事件(无论是击键鼠标)。
  2. 定义自定义事件无法解决问题,因为我需要重播同一事件。
  3. 我考虑过克隆事件,但AWTEvent不支持克隆。
  4. 通过序列化然后反序列化事件进行深层复制不起作用,因为调度的某些事件不可序列化。
  5. 我正在考虑通过反射重置事件中的任何状态变量,但这似乎很危险。
  6. 抱歉糟糕的格式化,我还没想出标记。 这里的任何帮助将不胜感激。

    固定:谢谢你的所有答案。修复(我自己找不到)是在调用时捕获会话超时异常。应用程序弹出一个对话框,要求用户重新进行身份验证。身份验证成功后,对话框已关闭。这让我感到惊讶。

    我不确定,但似乎事件在显示对话框时仍然卡在队列中,一旦对话框关闭,它就会被传递给控制器​​。无论如何。

3 个答案:

答案 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,重新发送它,然后是您在列表中保存的早期事件。