【问题标题】:JButton's action called on a focusLost event. How is it possible?JButton 的动作调用了 focusLost 事件。这怎么可能?
【发布时间】:2012-01-03 23:38:14
【问题描述】:

我们的一位客户报告了我们的应用程序出现异常。问题是,我完全无法理解如何重现此错误。

代码如下:

btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        popup.show(btn, 3, btn.getHeight());
    }
});

注意事项:

  • btnJButton 类型的最终局部变量。
  • popupJPopupMenu 类型的最终局部变量。

抛出以下异常:

java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
    at java.awt.Component.getLocationOnScreen_NoTreeLock(Unknown Source)
    at java.awt.Component.getLocationOnScreen(Unknown Source)
    at javax.swing.JPopupMenu.show(Unknown Source)
    at fr.def.iss.vd2.mod_site_watcher_gui.SiteElementPanel$4.actionPerformed(SiteElementPanel.java:117)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.plaf.basic.BasicButtonListener.focusLost(Unknown Source)
    at java.awt.Component.processFocusEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$000(Unknown Source)
    at java.awt.EventQueue$1.run(Unknown Source)
    at java.awt.EventQueue$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$2.run(Unknown Source)
    at java.awt.EventQueue$2.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

据我了解,show 方法抱怨 btn 没有显示。调用 actionPerformed 方法时,btn 怎么可能没有显示?

这个堆栈跟踪中最奇怪的是actionPerformed 方法似乎在处理FocusEvent 时被触发(实际上是focusLost)。

问题是:您能解释一下这个堆栈跟踪是如何发生的吗?

结语

感谢trashgod的建议,我找到了问题。

在 Windows 上,当按钮在被按下时消失时,就会触发其 ActionListeners,就像单击了按钮一样。这种行为可以在 Windows 上观察到,但在 Linux 上则不行。

我在 Oracle/Sun 错误数据库上提交了一个错误。这是链接:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7115421

(此链接将在几天内生效,经过 Java 团队审核后)。

感谢您的帮助。垃圾神和托马斯的回答很有帮助。

【问题讨论】:

  • 问题出在您没有显示的代码中
  • @kleopatra 也许是这样。周围的代码与问题无关,只会给它带来噪音。我只能说 1) JButton 的创建是启用或禁用的(之后未启用或禁用) 2) JButton 的容器可以从其层次结构中删除 3) JButton 的可见性 (setVisible) 是从未改变 4) actionPerformed 从未被我的代码显式调用 5) JButton 实例被添加到容器中,但它从未在其他地方引用。也没有代码尝试通过其容器访问 JButton
  • 正如许多人所说,SSCCE 是必需的。我们知道您在使用ActionListener 做什么,但我们不知道您还在使用按钮做什么。如果我们要帮助您,我们需要查看该代码,而您发布的那段代码不足以让我们找到问题。如果您只是将相同的按钮添加到一个空框架而无法重现问题,那么问题出在您的代码中的其他地方。
  • @Laf 这个问题根本无法重现。所以,不,我不能给你一个 SSCCE。问题的性质禁止这样做。
  • “周围的代码与问题无关”这显然是错误的,因为您显示的 sn-p 看起来很好并且不会产生错误:-) 尽全力重现问题 -这是您找到根本原因的唯一方法。没有,你在这里得到的一切都是疯狂的猜测

标签: java swing focus action jbutton


【解决方案1】:

一个可能的来源是竞争条件,它允许在接收者可见之前触发事件。验证您的 Swing GUI 对象是否仅在event dispatch thread 上构造和操作How to generate exceptions from RepaintManager 中引用的文章 Debugging Swing, the final summary 提到了几种自动化搜索的方法。

【讨论】:

  • 感谢您的建议。我对代码进行了手动静态分析,没有发现违反 EDT 的可能性。我还使用了一个在运行时查找 EDT 违规的工具(SwingExplorer),并尝试尽可能地刺激这个组件。该工具报告了一些违规行为,但与这段特定代码无关。不过,我修复了这些违规行为。
  • getLocationOnScreen(),会不会对端在一个不经意的dispose()之后变成了null?弹出窗口是否会导致 isShowing() 返回 false?
  • 这是一个或另一个...问题是如果它的对等点是null 或者如果按钮没有显示,如何调用actionPerformed
  • 我还看到了仅在某些平台上才会出现的潜在非 EDT 同步问题,例如PowerPC 上的 Mac 操作系统。你能在近似客户环境的东西上进行测试吗?
  • 你是对的。我现在可以重现这个错误,但只能在 Windows 上(我在 Linux 上开发)。我没有想过在另一个平台上尝试,因为我们使用跨平台的外观,并且堆栈跟踪中的任何内容似乎都与底层平台无关。我更新了我的问题以解释问题。
【解决方案2】:

•btn 是 JButton 类型的最终局部变量。

也许这就是问题所在。也许您引用了一个在屏幕上不可见的组件。

您应该使用:

JButton button = (JButton)e.getSource();

那么您肯定知道您正在引用生成事件的组件。

还要确保您没有同名的类变量。

【讨论】:

  • 监听器本身注册在btn。因此,getSource() 的返回值必须始终为btn,不是吗?
  • 我的意思是没有必要创建一个final 变量。您可以直接从 ActionEvent 中获取信息。它不太容易出错,并且在您可能希望在多个按钮之间共享 ActionListener 的其他情况下具有优势。无论如何,您还没有发布 SSCCE,所以我们只是在胡乱猜测。
  • 恐怕我的问题不能包含 SSCCE,因为问题是“如何重现这种行为”。如果我可以发布一个展示问题的示例(如您所建议的那样),我就不会问这个问题了,那么 :) 无论如何,谢谢您的意见。
  • @barjak 唯一有机会重现它的人是 you - 因为 you 是唯一在生产代码中看到它的人
  • @kleopatra 是的,但我现在有点缺乏想法 :) 我收到的答案对我帮助很大,所以我不后悔提出这个问题。
【解决方案3】:

查看DefaultButtonModel#setPressed(...)的源码我们看到如下:

if(!isPressed() && isArmed()) {
        ...
        fireActionPerformed(
            new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
                            getActionCommand(),
                            EventQueue.getMostRecentEventTime(),
                            modifiers));
} 

如您所见,ActionEvent 在按钮被“武装”时被触发,即有焦点但没有被按下。这与“FocusLost”事件一致。

【讨论】:

  • 调用者的代码(BasicButtonListener)是setArmed(false); setPressed(false);。我仍然不明白if 条件如何评估为true
  • +1 回想起来。我原以为所有平台都会使用DefaultButtonModel,但在Windows 上尝试cross-platform L&F 可能是值得的。
【解决方案4】:

改用特定于模式的操作:

final JButton button = new JButton();
button.addMouseListener(new MouseAdapter(){
    public void mouseClicked(MouseEvent e) {
       popup.show(btn, 3, btn.getHeight());
    }
})

【讨论】:

  • 感谢您的建议。它没有回答问题,但它可能是我特定问题的解决方法。
  • 这不是一个好主意。您应该使用ActionLlistener 管理按钮点击,否则您将错过从键盘生成的按钮点击(当焦点位于按钮上时按空格或输入)。
  • @Laf 你是对的,但是好处是如果只有鼠标点击可以触发popup.show,那么我们可以确定按钮实际上是显示的。我同意这不是一件好事,但它可能是一种解决方法。
【解决方案5】:

我无法抗拒,我只喜欢PopupFactory

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;

public class UsePopupFactory {

    private JFrame frame = new JFrame("PopupFactory Sample");
    private PopupFactory factory = PopupFactory.getSharedInstance();
    private Popup popup;

    public UsePopupFactory() {
        JPanel btnPanel = new JPanel();
        btnPanel.setBorder(new EmptyBorder(20, 20, 20, 20));
        btnPanel.setLayout(new GridLayout(0, 3));
        ActionListener actionListener = new ShowPopup(frame);
        JButton start3 = new JButton("Pick Me for Popup");
        JButton start = new JButton("Pick Me for Popup");
        JButton start2 = new JButton("Pick Me for Popup");
        btnPanel.add(start3);
        btnPanel.add(start);
        btnPanel.add(start2);
        start3.setVisible(false);
        start2.setVisible(false);
        start.addActionListener(actionListener);
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(btnPanel, BorderLayout.SOUTH);
        frame.setSize(new Dimension(d.width / 4, d.height / 4));
        frame.setVisible(true);
    }

    private class ShowPopup implements ActionListener {

        private Component component;

        ShowPopup(Component component) {
            this.component = component;
        }

        public synchronized void actionPerformed(ActionEvent actionEvent) {
            JPanel pnl = new JPanel();
            JComboBox combo = new JComboBox();
            JButton button = new JButton("any action");
            pnl.add(combo);
            pnl.add(button);
            pnl.setPreferredSize(new Dimension(250, 40));
            popup = factory.getPopup(component, pnl,
                    frame.getWidth() / 2 - pnl.getPreferredSize().width / 2,
                    frame.getHeight() / 2 - pnl.getPreferredSize().height / 2);
            popup.show();
            Timer timer = new Timer(3000, hider);
            timer.start();
        }
    }
    private Action hider = new AbstractAction() {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
            popup.hide();
        }
    };

    public static void main(final String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                UsePopupFactory uPF = new UsePopupFactory();
            }
        });
    }
}

【讨论】:

  • hmmm 令人难以置信的答案,我是即时写的,感谢您的回答
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多