barjak
barjak

Reputation: 11270

JButton's action called on a focusLost event. How is it possible?

One of our customer reported an exception in our application. The problem is, I am completely unable to understand how this bug can be reproduced.

Here is the code :

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

Notes :

The following exception was thrown :

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)

As far as I understand, the show method complains that btn is not showing. How is it possible that btn is not showing when its actionPerformed method is called ?

The strangest thing in this stacktrace is that the actionPerformed method seems to be triggered while a FocusEvent is being handled (a focusLost, actually).

The question is : can you explain how this stacktrace can possibly happen ?

Epilogue

Thanks to a suggestion from trashgod, I found the problem.

On Windows, when a button disappears while it is being pressed, then its ActionListeners are triggered, as if the button was clicked. This behavior can be observed on Windows, but not on Linux.

I filed a bug on the Oracle/Sun bug database. here is the link :

https://bugs.java.com/bugdatabase/view_bug?bug_id=7115421

(this link will become valid whithin a few days, after it is reviewed by the Java team).

Thanks for your help. The answers from trashgod and Thomas helped a lot.

Upvotes: 5

Views: 2848

Answers (5)

mKorbel
mKorbel

Reputation: 109813

I can't resist, I only to preffer 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();
            }
        });
    }
}

Upvotes: -1

trashgod
trashgod

Reputation: 205785

One possible source is a race condition that allows an event to fire before the recipient is visible. Verify that your Swing GUI objects are constructed and manuipulated only on the event dispatch thread. The article Debugging Swing, the final summary cited in How to generate exceptions from RepaintManager mentions several approaches to automating the search.

Upvotes: 6

camickr
camickr

Reputation: 324118

•btn is a final local variable of type JButton.

Maybe that is the problem. Maybe you have the reference to a component that is not visible on the screen.

Instead you should be using:

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

Then you know for sure you are referencing the component that generated the event.

Alsom make sure you don't have class variables of the same name.

Upvotes: 3

Thomas
Thomas

Reputation: 88707

Looking at the source code of DefaultButtonModel#setPressed(...) we see the following:

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

As you can see the an ActionEvent is fired when the button was "armed", i.e. had focus but was not pressed. That is consistent with the "FocusLost" event.

Upvotes: 2

Stanislav Levental
Stanislav Levental

Reputation: 2225

Use mode specific action instead:

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

Upvotes: 0

Related Questions