在 JPopupMenu 外部右键单击时执行某些操作(UIManager 问题)

doing something when right clicking outside JPopupMenu (UIManager issue)

我目前正试图在右键单击 JTable 时显示 JPopupMenu。 我已经通过不同的方式实现了这一点,但是 none 允许我做这个问题的内容:在我右键单击 table 之后执行一些代码,但是弹出菜单在屏幕上。

当显示弹出菜单并在其外部单击鼠标右键时,它会在不同的位置再次打开。所以它里面一定有一个听众告诉它这样做,但我找不到它。理想情况下,我会 @Override 它并执行我在 JTable 上右键单击时正在执行的相同代码。

因此,总结一下这个问题,当我在菜单外(或 table 单元格上,例如)右击时菜单仍然有焦点(或正在显示)时,如何触发一些代码)?

编辑: 仔细查看后,我确定了问题所在,但没有找到解决方案。 JPopupMenu 不是问题,而是 UIManager。这是显示我正在描述的问题的测试表单的代码:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;

public class PopupTesting extends javax.swing.JFrame {

    public PopupTesting() {
        initComponents();
    }

    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jTable1.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override
            public void mousePressed(java.awt.event.MouseEvent evt) {
                jTable1MousePressed(evt);
            }
        });
        jScrollPane1.setViewportView(jTable1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(61, 61, 61)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(83, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(69, 69, 69)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 267, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(121, Short.MAX_VALUE))
        );
        pack();
    }                      

    private void jTable1MousePressed(java.awt.event.MouseEvent evt) {                                     
        if (SwingUtilities.isRightMouseButton(evt)) {
            jTable1.setRowSelectionInterval(jTable1.rowAtPoint(evt.getPoint()), jTable1.rowAtPoint(evt.getPoint()));
            getPopup().show(jTable1, evt.getX(), evt.getY());
        }
    }                                    

    public static void main(String args[]) {
        try {
            javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            Logger.getLogger(PopupTesting.class.getName()).log(Level.SEVERE, null, ex);
        }
        java.awt.EventQueue.invokeLater(() -> {
            new PopupTesting().setVisible(true);
        });
    }

    private JPopupMenu getPopup() {
        if (jTable1.getSelectedRow() > -1) {
            JPopupMenu popup = new JPopupMenu();

            MouseListener listener = (new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    System.out.println("I'm a menu Item.");
                }
            });

            JMenuItem m = new JMenuItem("Regular Menu Item");
            m.addMouseListener(listener);
            popup.add(m);

            return popup;
        } return null;
    }

    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;               
}

如果删除该行:

javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());

和周围的trycatch,它完美地工作。

or on top of the table

table header 不是 table 的一部分,因此它不会响应鼠标事件。您需要向 table header.

添加一个单独的侦听器

when you click somewhere else and the menu is on screen, it both closes and triggers the click event where you tried to click

如果您单击 table.

的单元格,在 Windows 7 上使用 JDK8 对我来说效果很好

如果问题仍然存在,那么 post 一个 SSCCE 来证明问题。

编辑:

正如 MadProgrammer 在他的评论中建议的那样,应该使用 setComponentPoupMenu(...) 方法。这是较新的 API。我不知道这两种方法之间有什么区别,但是在我测试的两个 LAF 上使用该方法对我都有效。

编辑2:

再一次,它对我来说仍然很好用。请注意,您不应将 MouseListener 添加到菜单项。您使用 ActionListener 来处理菜单项的点击:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class TableRightClick extends JFrame implements ActionListener
{
    JPopupMenu popup;

    public TableRightClick()
    {
        popup = new JPopupMenu();
        popup.add( new JMenuItem("Do Something1") );
        JMenuItem menuItem = new JMenuItem("ActionPerformed");
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.ALT_MASK));

        menuItem.addActionListener( this );
        popup.add( menuItem );

        JTable table = new JTable(10, 5);

        table.addMouseListener( new MouseAdapter()
        {
            public void mousePressed(MouseEvent e)
            {
                JTable source = (JTable)e.getSource();
                int row = source.rowAtPoint( e.getPoint() );
                int column = source.columnAtPoint( e.getPoint() );

                if (! source.isRowSelected(row))
                    source.changeSelection(row, column, false, false);
            }
        });

        table.setComponentPopupMenu( popup );

        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        getContentPane().add( new JScrollPane(table) );

        JMenuBar menuBar = new JMenuBar();
        setJMenuBar( menuBar );
        menuBar.add( popup );
    }

    public void actionPerformed(ActionEvent e)
    {
        Component c = (Component)e.getSource();
        JPopupMenu popup = (JPopupMenu)c.getParent();
        JTable table = (JTable)popup.getInvoker();
        System.out.println(table.getSelectedRow() + " : " + table.getSelectedColumn());
    }

    public static void main(String[] args)
    {
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception ex) { System.out.println(ex); }

        TableRightClick frame = new TableRightClick();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }
}

好吧,在尝试解决这个问题很多小时后,我最终做出了一个不可靠的修复。执行我要求的代码在这里:

import java.awt.AWTException;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

public class PopupTesting extends javax.swing.JFrame {

    public PopupTesting() {
        initComponents();
        jTable.setComponentPopupMenu(getPopup());
    }

    volatile int lastMouse = 1;

    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTable = new javax.swing.JTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        jTable.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override
            public void mousePressed(java.awt.event.MouseEvent evt) {
                jTableMousePressed(evt);
            }
        });

        jScrollPane1.setViewportView(jTable);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(61, 61, 61)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(83, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(69, 69, 69)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 267, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(121, Short.MAX_VALUE))
        );
        pack();
    }

    private void jTableMousePressed(java.awt.event.MouseEvent evt) {                                    
        lastMouse = evt.getButton();
        if (lastMouse == 3) lastMouse--;
        lastMouse = InputEvent.getMaskForButton(lastMouse);
    }                                 


    public static void main(String args[]) {
        try {
            javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            Logger.getLogger(PopupTesting.class.getName()).log(Level.SEVERE, null, ex);
        }
        java.awt.EventQueue.invokeLater(() -> {
            new PopupTesting().setVisible(true);
        });
    }

    private JPopupMenu getPopup() {
        JPopupMenu popup = new JPopupMenu();

        JMenuItem m = new JMenuItem("Regular Menu Item");
        m.addActionListener((ActionEvent e) -> {
            System.out.println("I'm a menu Item.");
        });
        popup.add(m);
        PopupTesting frame = this;
        popup.addAncestorListener( new AncestorListener() {
            @Override
            public void ancestorAdded(AncestorEvent event) {
                Point mousePoint = MouseInfo.getPointerInfo().getLocation();
                mousePoint = SwingUtilities.convertPoint(frame, parseInt(mousePoint.getX() - getX()), parseInt(mousePoint.getY() - getY()), jTable);
                jTable.setRowSelectionInterval(jTable.rowAtPoint(mousePoint), jTable.rowAtPoint(mousePoint));
            }
            @Override public void ancestorRemoved(AncestorEvent event) {
                try {
                    Robot bot = new Robot();
                    bot.mousePress(lastMouse);
                    bot.mouseRelease(lastMouse);
                } catch (AWTException ex) {
                    Logger.getLogger(PopupTesting.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            @Override public void ancestorMoved(AncestorEvent event) {}
        });
        return popup;
    }


    private int parseInt(Double val) {
        return val.intValue();
    }

    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable;               
}

尽管 setComponentPopupMenu() 在仍然可见时再次显示弹出窗口,它仍然不会触发 MouseListeners。

我 "fixed" 的方法是使用 AncestorListener,当显示弹出菜单时会触发 ancestorAdded,当退出菜单时会触发 ancestorRemoved屏幕。

对于 ancestorAdded,我只是简单地做到了,无论何时显示菜单,它也选择了它后面的单元格(花了很多时间来弄清楚如何从 MouseInfo 就像我从 MouseEvent 得到的那样,但我到了那里)。

有了ancestorRemoved,我不得不"cheat"。基本上,每次我在 table 上单击 (MousePressed) 时,我都会存储使用哪个按钮执行此操作。 然后,一旦 ancestorRemoved 被触发,它会创建一个 Robot 重复最后一次鼠标点击(如果是鼠标 1,它会触发鼠标 1,鼠标 2 会触发鼠标 2,等等)。

此解决方案有效,但它不允许我在退出 JPopupMenu 时按住鼠标按钮。如果有人相信知道解决方案,请告诉我,但现在,问题已解决。