【问题标题】:Mouse Clicks being cached?鼠标点击被缓存?
【发布时间】:2013-11-14 03:37:00
【问题描述】:

我有一个带有登录屏幕的 java swing 应用程序。登录屏幕有一个提交按钮,用于在输入用户凭据后按下。当按下按钮时,等待光标会使用其玻璃窗格在窗口上方抛出。还有一个默认的鼠标适配器,对任何鼠标操作都不做任何事情。

private final static MouseAdapter mouseAdapter =
        new MouseAdapter() {};

/** Sets cursor for specified component to Wait cursor */
public static void startWaitCursor(JComponent component) {
    log.debug("startWaitCursor()");
    RootPaneContainer root =
            ((RootPaneContainer) component.getTopLevelAncestor());
    Component glass = root.getGlassPane();
    glass.setCursor(WAIT_CURSOR);
    glass.addMouseListener(mouseAdapter);
    glass.setVisible(true);
    //force repaint of glass pane in 20ms for more responsive GUI
    glass.repaint(20);
}

public static void stopWaitCursor(JComponent component) {
    log.debug("stopWaitCursor()");
    RootPaneContainer root =
            ((RootPaneContainer) component.getTopLevelAncestor());
    Component glass = root.getGlassPane();

    glass.setCursor(DEFAULT_CURSOR);
    glass.removeMouseListener(mouseAdapter);
    //force repaint of glass pane in 20ms for more responsive GUI
    glass.repaint(20);
    glass.setVisible(false);
}

我曾假设此设置可以在后端方法发生时保护我免受多次点击/按键的影响。我发现事实并非如此。所以在 ButtonListener.actionPerformed 中,我放了一些如下的逻辑:

static boolean waiting = false; 
class ButtonListener implements ActionListener {
      ButtonListener() {
          super();
      }

      public void actionPerformed(ActionEvent e) {      
          log.info("LoginWindow.ButtonListener.actionPerformed()");
          LoginWindow.this.repaint(50);
          if (!waiting) {
              try {
                  waiting = true;
                  verifyLogin();        
              } finally {
                  waiting = false;
              }
          }
      }
}

我发现这可以保护我免受按键的影响,但不能防止鼠标点击!如果我在执行 verifyLogin() 时反复按下提交按钮,鼠标点击似乎被缓存在某处,验证登录完成后,每次鼠标点击都会被处理!

我对这里发生的事情感到非常困惑。有人有想法吗?

更新:

嗯,按照 Cyrille Ka 建议的方法:即在单独的线程中执行 verifyLogin() 方法并禁用按钮,我现在在多次鼠标单击后只收到两个事件,但第二个事件仍然很烦人。

代码现在是:

      public void actionPerformed(ActionEvent e) {      
          loginButton.setEnabled(false);
          log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
                  new Date(e.getWhen()));
          LoginWindow.this.repaint(50);
          SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                verifyLogin();
                loginButton.setEnabled(true);

            }});
      }

但第二个事件仍然进入。我的日志显示第二个事件发生在第一个事件之后大约 280 毫秒,但直到 4 秒后才执行,尽管 setEnabled() 是第一件事actionPerformed() 事件发生了。

2013-11-13 10:33:57,186 [AWT-EventQueue-0] 信息 c.a.r.s.c.g.LoginWindow - LoginWindow.ButtonListener.actionPerformed()。事件发生在 11 月 13 日 2013 10:33:57.175 2013-11-13 10:34:01,188 [AWT-EventQueue-0] 信息 c.a.r.s.c.g.LoginWindow - LoginWindow.ButtonListener.actionPerformed()。事件发生在 11 月 13 日 2013 10:33:57.453

我想我可以做一个 hack 并丢弃第二个旧的事件或其他东西,但这感觉很难看。这应该不是那么难,我一直在想。

更新 2: 来自 JComponent.java 的 setEnabled() 注释

 * <p>Note: Disabling a lightweight component does not prevent it from
 * receiving MouseEvents.

既然所有的 Swing 组件都是轻量级的,而且 setEnabled 不会阻止组件接收鼠标事件,那么是什么阻止了呢?

【问题讨论】:

  • 您在尝试登录时尝试过myButton.setEnabled(false); 之类的方法吗?
  • 是的,这也不起作用。我在有和没有等待光标的情况下都试过了。在所有情况下,鼠标点击都会被存储。
  • 它应该可以工作。但是,如果您不从 GUI 返回并在事件侦听器中完成工作,那么可能在事件侦听器返回之前未注册禁用。

标签: java swing jbutton actionlistener mouselistener


【解决方案1】:

我曾假设此设置可以在后端方法发生时保护我免受多次点击/按键的影响。我发现事实并非如此。

The Glass Pane 上的 Swing 教程中的部分提供了如何执行此操作的示例。不记得它是否也只处理 MouseEvents 或 KeyEvents。

无论如何,您也可以查看Disabled Glass Pane,它确实处理这两个事件。

【讨论】:

  • 谢谢。我已经在使用 Glass Pane。我以为它会阻止所有输入,但事实并非如此。我仍在使用它,因为它与我的等待光标绑定在一起,现在,通过我基于 AWTEvent.getWhen() 的解决方案,我得到了我正在寻找的控件。正如我在那里所说,令人惊讶的是,没有简单的 API 可以防止多次执行。我使用的 API (getWhen()) 很简单,但我找不到任何文档推荐将其用于此(或任何其他)目的。
  • 看着你的 DisabledGlassPane,我在鼠标点击时做了同样的事情。我只是添加了一个新的 MouseAdapter() 和一个监听器。我什至可能很久​​以前就从你的样本中得到了这个想法。它不起作用,即使我实现了适配器来使用事件,它也不起作用。这是因为鼠标事件被缓存在操作系统的某个地方,一旦线程被解除阻塞就会返回。唯一可行的方法是,如果它们的“时间”早于第一个事件启动的任务完成,则稍后将其丢弃。
  • @SteveCohen,对我来说很好(在 Windows 7 上),否则我不会创建博客。事件不会被缓存,它们会被忽略。也许您的问题是您试图在事件调度线程中执行所有代码。这不正确。长时间运行的任务应该在单独的线程中执行。阅读 Concurrency 上的 Swing 教程部分了解更多信息。
  • 这就是您所说的(查看我更新的代码) SwingUtilities.invokeLater() 不会在单独的线程中调用,而只是将其推送到由调度线程,如果它确实在一个完全独立的线程中,它会工作吗?我真的很想明白这一点。尽管我确实发布了解决方法,但我仍然觉得我对这里发生的事情没有完全了解。
  • 嘿,谢谢,我以前从未找到 SwingWorker。看起来它也可能解决了我的问题。
【解决方案2】:

我认为verifyLogin() 在登录完成之前一直处于阻塞状态。通过这样做,您只是阻塞了 Swing 事件调度程序线程。当线程可用时,来自操作系统的事件仍在排队等待发送到您的 GUI。

有两种方法可以防止您的用户重复点击:

  • 只需禁用按钮:button.setEnabled(false); 并在进程完成后重新启用它。
  • 启动一个模态对话框(例如带有等待动画)并在进程完成时将其移除。

编辑: 一般来说,您应该从事件侦听器中快速返回,因为您不想阻止所有 GUI,只阻止某些部分,并且无论如何它会让您的应用感觉迟缓(如果窗口被移动或其他东西,则不会在此期间重新绘制窗口)。使用Thread 启动运行verifyLogin() 的任务,同时禁用您的按钮。

【讨论】:

  • @Cyrille_Ka “通过这样做”。你指的是哪个“这个”?
  • @Cyrille_Ka - 我假设你的意思是等待光标的东西。我认为你可能是对的。但是,我仍然对为什么我的等待逻辑适用于按键而不适用于鼠标点击感到困惑。为什么操作系统不排队所有事件,而不仅仅是鼠标事件?
  • 我的意思是在你的事件监听器中调用verifyLogin。您说“如果我在执行 verifyLogin() 时反复按下提交按钮”,那么您在未使用此事件时发送点击事件。
  • 按键的行为不应有所不同,IMO。也许焦点转移到您的应用程序内外的其他东西上?
【解决方案3】:

这行得通:

class ButtonListener implements ActionListener {
    long previousEventEnd;
    public void actionPerformed(ActionEvent e) {
        if (e.getWhen() <= previousEventEnd ) {
            log.tracef("discarding stale event, event occurred at  %1$tb %1$te %1$tY %1$tT.%1$tL",
                    new Date(e.getWhen()));
            return;
        }
        log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
                new Date(e.getWhen()));
        LoginWindow.this.repaint(50);
        SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                verifyLogin();
                previousEventEnd = System.currentTimeMillis();
            }
        });
    }
}

我不得不承认我很惊讶。我通常为 Java 的诋毁者辩护。在这一点上,我没有任何辩护。这应该没有必要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-31
    • 2014-01-15
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多