3

I need to display a swing popup with my custom component. The popup should stay visible, until I hide it myself, but shouldn't get focus.

I have a code written by some other developer that does it in the following way:

       popupMenu = new JPopupMenu();
       popupMenu.add(myCustomComponent, BorderLayout.CENTER);
       popupMenu.setFocusable(false);
       popupMenu.setVisible(true);
       popupMenu.show(parentComponent, x, y);

This seems to work, but has a bug - when the popup is visible, first mouse click outside the component is consumed by the popup. So I need to click twice to set focus to another component.

How can I fix it? Or what is correct way to make the popup?

UPDATE

At last I've managed to reproduce my problem in short code fragment. Thanks to Guillaume Polet for giving me a starting point.

Here's the code:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.*;

public class TestJPopup {

    protected void initUI() {
        JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JTextField textField = new JTextField("Some text field");
        frame.add(textField, BorderLayout.WEST);
        final JButton buttonToHit = new JButton("Hit me");
        buttonToHit.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
            }
        });
        frame.add(buttonToHit);
        frame.setSize(200, 100);
        frame.setVisible(true);

        final JPopupMenu popup = new JPopupMenu();
        popup.add(new JLabel("<html>Hey!<br>I'm the popup window!</html>"),
                BorderLayout.NORTH);
        popup.setFocusable(false);
        popup.setVisible(true);
        popup.show(textField, 60, 60);

        // I want to activate popup when user clicks in the text field
        textField.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (popup != null) {
                    popup.show(textField, 60, 60);
                }
            }
        });
    }

    public static void main(String[] args) throws Exception {
        Class lnfClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsLookAndFeel", true,
                Thread.currentThread().getContextClassLoader());
        LookAndFeel feel = (LookAndFeel) lnfClass.newInstance();
        UIManager.setLookAndFeel(feel);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new TestJPopup().initUI();
            }
        });
    }
}

Two critical moments:

  • Windows look and feel used (with default not reproducible)
  • Mouse listener attached to text field in main frame
Ilya Ivanov
  • 2,379
  • 1
  • 21
  • 24
  • 1
    Post an [SSCCE](http://sscce.org) that reproduces the problem. – Guillaume Polet Sep 26 '12 at 17:21
  • Don't use a JPopup, use a JWindow instead. You're going to have to deal with keyboard and mouse actions your self, but the result will be close to the same as not be of an issue... – MadProgrammer Sep 26 '12 at 20:18
  • asking for an SSCCE is guiding you to a - very successful - problem solution strateg: as you have learned from @GuillaumePolet answer, your problem is somewhere else. Having excluded the part you were focused is valuable information. Rinse and repeat :-) – kleopatra Sep 27 '12 at 09:53
  • @kleopatra Well, yes. That helped in some way :) now I managed to localize the problem – Ilya Ivanov Sep 27 '12 at 10:39
  • cool - I see it (unfortunately, no idea why that happens, only some further exclusion - not a heavyweight/lightweight popup issue, happens whether or not the popup fits in over the frame area) – kleopatra Sep 27 '12 at 11:22
  • +1 please see my answer with lined to another post – mKorbel Sep 27 '12 at 17:31

5 Answers5

4

Not an answer, but just an example SSCCE in which I can't currently reproduce the behaviour you described. Maybe start from this code, try to reproduce the error and the edit your post with modified non-working code.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class TestJPopup {

    protected void initUI() {
        JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JLabel leftLabel = new JLabel("Left");
        frame.add(leftLabel, BorderLayout.WEST);
        final JButton buttonToHit = new JButton("Hit me");
        buttonToHit.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
            }
        });
        frame.add(buttonToHit);
        frame.setSize(500, 400);
        frame.setVisible(true);
        JPopupMenu popupMenu = new JPopupMenu();
        popupMenu.add(new JLabel("<html>A Custom<br>component<br>made to<br> simulate <br>your custom component</html>"),
                BorderLayout.NORTH);
        JTextField textfield = new JTextField(30);
        popupMenu.add(textfield);
        popupMenu.setFocusable(false);
        popupMenu.setVisible(true);
        popupMenu.show(leftLabel, 20, 20);
        // Let's force the focus to be in a component in the popupMenu
        textfield.requestFocusInWindow();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestJPopup().initUI();
            }
        });
    }
}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • 2
    Thanks for your effort! I'm also trying now to extract my buggy control to new fresh JFrame... and it also seems to work. Probably there's something else in application that enables this bug. If I manage to find this thing, I will pase my code here. – Ilya Ivanov Sep 26 '12 at 18:37
  • 2
    this could be possible only by replace JPopup with JWindow or un_decorated JDialog, JPopup isn't designated for this job – mKorbel Sep 27 '12 at 08:12
  • The problem can be reproduced if application uses Windows look and feel and if a mouse listener attached to one of components (see update in my question) – Ilya Ivanov Sep 27 '12 at 10:37
2

Not a solution, but:

Looks like a bug to me, even a plain componentPopup exhibits the same mis-behaviour (in winLAF and Nimbus, not in Metal):

JTextField field = new JTextField("some popup owner");
JPopupMenu menu = new JPopupMenu();
menu.add("dummy");
field.setComponentPopupMenu(menu);
Action action = new AbstractAction("hit me!") {

    @Override
    public void actionPerformed(ActionEvent e) {
        LOG.info("got hit!");
    }
};
JComponent content = new JPanel();
content.add(new JButton(action));
content.add(field);
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • 2
    same issue with JMenu, for > Java6 for windows (systemlookandfeel ???) commented similair question twice or more times, the same issue has popup equivalent in MsAccess too – mKorbel Sep 27 '12 at 12:10
  • dooh .. you mean in a native MsAccess app? That's a bit weird because the bug does not appear in Metal, so doesn't look as a OS thingy to me. Anyway, good to know :-) – kleopatra Sep 27 '12 at 12:28
  • @mKorbel may be you know some workaround for this? Or please paste links to other discussions about the issue. – Ilya Ivanov Sep 27 '12 at 12:35
  • I can't found this questions right now, there I'm only commented (I'm searched in my profile, but tag Java bug some i*** removed without any retags eeerrgght) – mKorbel Sep 27 '12 at 12:35
  • 1
    @Ilya Ivanov not this I don't know, this issue came from `Native OS`, the same for `JPopup`, `JMenu` and nice could (sorry my endless lazyness kill me right now :-) be to test for `JComboBoxes popup`, but presented in Java5, 6 and 7, but `Java4` have got little bit different API for `JPopup`, another way could be to use `heavyweight awt.Popup`, but maybe caused with the same issues as `lightwieght swing.JPopup` – mKorbel Sep 27 '12 at 12:40
  • @Ilya Ivanov I have got only the code for testing JPopup anf JMenu no issue to generated from two SSCCE's in your thread :-) – mKorbel Sep 27 '12 at 12:41
  • guys: please read my comments before speculating :-) a) it's _not_ a heavyweight vs. lightweight problem b) I really doubt it's a native OS (aka: win) issue as it doesn't happen with MetalLAF: I would expect a _native_ issue to happen always. Hmm .. or is it a win native _feature_ that somehow gets disabled by Metal? Trying: open popup on this page, then click save edits ... does ... save the edit. – kleopatra Sep 27 '12 at 12:45
  • :-) :-) :-) :-) :-) faster than light – mKorbel Sep 27 '12 at 12:50
2

for quick research and/or for future readers,

  • this issue is reproducible and presented for,

    a) JPopup

    b) JMenu

  • tested on jdk1.6.0_25 and jdk1.7.0_04,

  • same issue on WinXp and Win7,

  • for Look and Feel to SystemLookAndFeel / WindowsLookAndFeel,

Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
2

Here's a possible workaround with JWindow instead of JPopupMenu, that was proposed by mKorbel in comments:

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class TestJPopup {

    protected void initUI() {
        final JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JTextField textField = new JTextField("Some text field");
        frame.add(textField, BorderLayout.WEST);
        final JButton buttonToHit = new JButton("Hit me");
        buttonToHit.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
            }
        });
        frame.add(buttonToHit);
        frame.setSize(200, 70);
        frame.setVisible(true);

        final JWindow popup = new JWindow();
        popup.getContentPane().add(new JLabel("<html>Hey!<br>I'm the popup window!</html>"),
                BorderLayout.NORTH);
        popup.setLocation(frame.getLocation().x + 60, frame.getLocation().y + 60);
        popup.pack();
        popup.setFocusable(false);
        popup.setVisible(true);

        // I want to activate popup when user clicks in the text field
        textField.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (popup != null) {
                    popup.setVisible(true);
                    popup.setLocation(frame.getLocation().x + 60, frame.getLocation().y + 60);
                    popup.toFront();
                }
            }
        });

        textField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                if (popup != null) {
                    popup.setVisible(false);
                }
            }
        });
    }

    public static void main(String[] args) throws Exception {
        Class lnfClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsLookAndFeel", true,
                Thread.currentThread().getContextClassLoader());
        LookAndFeel feel = (LookAndFeel) lnfClass.newInstance();
        UIManager.setLookAndFeel(feel);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new TestJPopup().initUI();
            }
        });
    }
}
Ilya Ivanov
  • 2,379
  • 1
  • 21
  • 24
  • 1
    have look at [JCalendar by Kai Toedter(load codesource)](http://www.toedter.com/en/jcalendar/), excelent workaround for popup window, – mKorbel Sep 28 '12 at 11:43
0

Here is the magic line that fixes the problem:

UIManager.put("PopupMenu.consumeEventOnClose", Boolean.FALSE);

I found this after looking into the source code for the BasicPopupMenuUI class. Apparently this behaviour is a deliberate design choice according to the following comments in the code, but it sure feels like a bug to me.

        // Ask UIManager about should we consume event that closes
        // popup. This made to match native apps behaviour.

By the way, it happens in Java 5 and 6 too.

kjohnson
  • 110
  • 1
  • 4