Swing persistent popup

随声附和 提交于 2019-12-10 10:14:32

问题


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

回答1:


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();
            }
        });
    }
}



回答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);



回答3:


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,




回答4:


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();
            }
        });
    }
}



回答5:


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.



来源:https://stackoverflow.com/questions/12606675/swing-persistent-popup

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!