Действието на JButton извиква събитие focusLost. Как е възможно?

Един от нашите клиенти съобщи за изключение в нашето приложение. Проблемът е, че не мога да разбера как може да се възпроизведе този бъг.

Ето кода:

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

Бележки:

  • btn е крайна локална променлива от тип JButton.
  • popup е крайна локална променлива от тип JPopupMenu.

Беше хвърлено следното изключение:

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 не се показва. Как е възможно btn да не се показва, когато се извика неговият метод actionPerformed?

Най-странното нещо в това проследяване на стека е, че методът actionPerformed изглежда се задейства, докато FocusEvent се обработва (всъщност focusLost).

Въпросът е: можете ли да обясните как е възможно това проследяване на стека да се случи?

Епилог

Благодарение на предложение от trashgod, открих проблема.

В Windows, когато бутон изчезне, докато е натиснат, тогава се задействат неговите ActionListeners, сякаш бутонът е щракнат. Това поведение може да се наблюдава в Windows, но не и в Linux.

Подадох сигнал за грешка в базата данни за грешки на Oracle/Sun. ето линка:

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

(тази връзка ще стане валидна в рамките на няколко дни, след като бъде прегледана от екипа на Java).

Благодаря за вашата помощ. Отговорите на trashgod и Thomas помогнаха много.


person barjak    schedule 22.11.2011    source източник
comment
проблемът е в кода, който не показваш   -  person kleopatra    schedule 22.11.2011
comment
@kleopatra Може би е така. Околният код не е подходящ за въпроса и само би донесъл шум към него. Мога само да кажа, че 1) JButton се създава или активиран, или деактивиран (той не е активиран или деактивиран след това) 2) Контейнерът на JButton може да бъде премахнат от неговата йерархия 3) видимостта (setVisible) на JButton никога не се променя 4) actionPerformed никога не се извиква изрично от моя код 5) екземплярът JButton се добавя към контейнер, но никога не се споменава другаде. Няма код, който да се опитва да получи достъп до JButton през неговия контейнер   -  person barjak    schedule 22.11.2011
comment
Както мнозина казаха, изисква се SSCCE. Знаем какво правите с ActionListener, но не знаем какво друго правите с бутона си. Трябва да видим този код, ако искаме да ви помогнем, а частта от кода, която сте публикували, е недостатъчна, за да открием проблема. Ако проблемът не може да бъде възпроизведен, ако просто добавите същия бутон към празна рамка, тогава проблемът се крие някъде другаде във вашия код.   -  person Laf    schedule 22.11.2011
comment
@Laf проблемът изобщо не може да бъде възпроизведен. Така че не, не мога да ви дам SSCCE. Естеството на въпроса забранява това.   -  person barjak    schedule 22.11.2011
comment
Околният код не е подходящ за въпроса, който очевидно е грешен, тъй като фрагментът, който показвате, изглежда добре и не може да доведе до грешката :-) положете всичките си усилия, за да възпроизведете проблема - това е единственият начин да намерите основната причина. Без това всичко, което получавате тук, са диви предположения   -  person kleopatra    schedule 23.11.2011
comment
положете всички усилия да възпроизведете проблема, точно това се опитвам да постигна: да възпроизведа проблема, който се е случил само веднъж, на машина, до която нямам достъп. Ако мога да възпроизведа проблема, аз печеля! Ако наистина искате пълния код на класа, ето го: pastebin.com/J2iU2kpd   -  person barjak    schedule 23.11.2011


Отговори (5)


Един възможен източник е състояние на състезание, което позволява събитие да се задейства, преди получателят да е видим. Уверете се, че вашите Swing GUI обекти са конструирани и манипулирани само на нишка за изпращане на събития. Статията Swing за отстраняване на грешки, окончателното резюме, цитиран в Как да генерирам изключения от RepaintManager споменава няколко подхода за автоматизиране на търсенето.

person trashgod    schedule 22.11.2011
comment
Благодаря за предложението Направих ръчен статичен анализ на кода и не видях възможност за нарушение на EDT. Използвах също инструмент за намиране на нарушения на EDT по време на изпълнение (SwingExplorer) и се опитах да стимулирам този компонент колкото е възможно повече. Инструментът съобщи за някои нарушения, но нищо свързано с този конкретен код. Все пак поправих тези нарушения. - person barjak; 23.11.2011
comment
Гледайки getLocationOnScreen(), може партньорът да е станал null след неволно dispose()? Може ли изскачащият прозорец да накара isShowing() да върне false? - person trashgod; 23.11.2011
comment
Това е едното или другото... Въпросът е как може да се извика actionPerformed, ако неговият партньор е null или ако бутонът не се показва? - person barjak; 23.11.2011
comment
Партньорът не трябва да изчезва, но е собственост на хоста. Неправилно поставен dispose() може да бъде по-трудно да се намери. Направете някое от предупрежденията в isShowing() приложите? - person trashgod; 24.11.2011
comment
Не, бутонът не е в панел за превъртане и не може да бъде скрит от друг компонент. Но контейнерът на бутона може да бъде премахнат от неговата йерархия. - person barjak; 24.11.2011
comment
Виждал съм също латентни, не-EDT проблеми със синхронизацията, които са очевидни само на определени платформи, напр. Mac OS на PowerPC. Можете ли да тествате върху нещо, което се доближава до средата на клиента? - person trashgod; 24.11.2011
comment
Ти беше права. Мога да възпроизведа този бъг сега, но само на Windows (разработвам на Linux). Не съм мислил да опитам на друга платформа, тъй като използваме междуплатформен външен вид и усещане и нищо в проследяването на стека изглежда не е свързано с основната платформа. Актуализирах въпроса си, за да обясня проблема. - person barjak; 24.11.2011

•btn е крайна локална променлива от тип JButton.

Може би това е проблемът. Може би имате препратка към компонент, който не се вижда на екрана.

Вместо това трябва да използвате:

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

Тогава знаете със сигурност, че препращате към компонента, който е генерирал събитието.

Също така се уверете, че нямате променливи на класа със същото име.

person camickr    schedule 22.11.2011
comment
Самият слушател е регистриран на btn. Следователно върнатата стойност на getSource() винаги трябва да бъде btn, нали? - person barjak; 22.11.2011
comment
Искам да кажа, че няма нужда от създаване на променлива final. Можете да получите информацията директно от ActionEvent. Той е по-малко склонен към грешки и има предимства в други ситуации, в които може да искате да споделите ActionListener между множество бутони. Във всеки случай не сте публикували SSCCE, така че ние просто правим смели предположения. - person camickr; 22.11.2011
comment
Страхувам се, че въпросът ми не може да съдържа SSCCE, защото въпросът е как е възможно да се възпроизведе това поведение. Ако можех да публикувам пример, който показва проблема (както предлагате), тогава нямаше да задам въпроса :) Благодаря за вашия принос, все пак. - person barjak; 22.11.2011
comment
@barjak единственият, който има и най-малък шанс да го възпроизведе, е ти - защото ти си единственият, който го вижда в производствения си код - person kleopatra; 23.11.2011
comment
@kleopatra да, но в момента ми липсват идеи :) Отговорите, които получих, ми помогнаха много, така че не съжалявам, че зададох този въпрос. - person barjak; 23.11.2011

Разглеждайки изходния код на DefaultButtonModel#setPressed(...) виждаме следното:

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

Както можете да видите, ActionEvent се задейства, когато бутонът е бил "включен", т.е. имал е фокус, но не е бил натиснат. Това е в съответствие със събитието „FocusLost“.

person Thomas    schedule 22.11.2011
comment
Кодът на обаждащия се (в BasicButtonListener) е setArmed(false); setPressed(false);. Все още не разбирам как условието if може да се оцени на true. - person barjak; 22.11.2011
comment
+1 в ретроспекция. Бих си помислил, че всички платформи ще използват DefaultButtonModel, но може би си струва да опитате междуплатформен L&F в Windows. - person trashgod; 24.11.2011

Вместо това използвайте конкретно действие за режим:

final JButton button = new JButton();
button.addMouseListener(new MouseAdapter(){
    public void mouseClicked(MouseEvent e) {
       popup.show(btn, 3, btn.getHeight());
    }
})
person Stanislav Levental    schedule 22.11.2011
comment
Благодаря за предложението Това не отговаря на въпроса, но може да е решение за моя конкретен проблем. - person barjak; 22.11.2011
comment
Това не е добра идея. Трябва да управлявате щракванията върху бутони, като използвате ActionLlistener, в противен случай ще пропуснете щраквания върху бутони, генерирани от клавиатурата (натискане на интервал или enter, когато фокусът е върху бутона). - person Laf; 22.11.2011
comment
@Laf, прав си, но предимството е, че ако само щракване на мишката може да задейства popup.show, тогава можем да сме сигурни, че бутонът наистина се показва. Съгласен съм, че не е чудесно да се направи, но може да е заобиколно решение. - person barjak; 22.11.2011

Не мога да устоя, предпочитам само 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();
            }
        });
    }
}
person mKorbel    schedule 22.11.2011
comment
хмм невероятен отговор, написах го в движение, благодаря за отговора - person mKorbel; 23.11.2011