Java JMenuBar SelectionModel ChangeListener仅激发一次

Java JMenuBar SelectionModel ChangeListener仅激发一次,java,swing,jmenuitem,jmenubar,changelistener,Java,Swing,Jmenuitem,Jmenubar,Changelistener,我试图让我的JMenuBar模拟Firefox和iTunes菜单栏的行为。行为:菜单栏最初是隐藏的。但是,当您按下Alt时,菜单栏会出现(选中第一个项目),而当您没有选择菜单项时,菜单栏会消失。我的想法是通过其SelectionModel上的changelister来监听对JMenuBar的选择更改 但是,附着的对象的行为不符合要求。加载帧时,JMenuBar不可见。当您按下Alt时,菜单栏将显示选定的第一个菜单(感谢WindowsLookAndFeel)。但是,每次按下Alt键都不会触发任何C

我试图让我的
JMenuBar
模拟Firefox和iTunes菜单栏的行为。行为:菜单栏最初是隐藏的。但是,当您按下
Alt
时,菜单栏会出现(选中第一个项目),而当您没有选择菜单项时,菜单栏会消失。我的想法是通过其
SelectionModel
上的
changelister
来监听对
JMenuBar
的选择更改

但是,附着的对象的行为不符合要求。加载帧时,
JMenuBar
不可见。当您按下
Alt
时,菜单栏将显示选定的第一个菜单(感谢
WindowsLookAndFeel
)。但是,每次按下
Alt
键都不会触发任何
ChangeEvents
。我不明白为什么

有人有光吗

public class MenuBarTest extends javax.swing.JFrame {

    public MenuBarTest() {
        initComponents();
        jMenuBar1.setVisible(false);
        jMenuBar1.getSelectionModel().addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                System.out.println(e.toString());
                jMenuBar1.setVisible(jMenuBar1.isSelected());
                System.out.println(jMenuBar1.isSelected());
                System.out.println(jMenuBar1.getSelectionModel().isSelected());
            }
        });
    }

    private void initComponents() {

        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu1 = new javax.swing.JMenu();
        jMenuItem1 = new javax.swing.JMenuItem();
        jMenu2 = new javax.swing.JMenu();
        jMenuItem2 = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jMenu1.setText("File");
        jMenuItem1.setText("jMenuItem1");
        jMenu1.add(jMenuItem1);
        jMenuBar1.add(jMenu1);
        jMenu2.setText("Edit");
        jMenuItem2.setText("jMenuItem2");
        jMenu2.add(jMenuItem2);
        jMenuBar1.add(jMenu2);
        setJMenuBar(jMenuBar1);
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGap(0, 400, Short.MAX_VALUE));
        layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGap(0, 279, Short.MAX_VALUE));

        pack();
    }

    public static void main(String args[]) {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        }
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new NewClass().setVisible(true);
            }
        });
    }
    private javax.swing.JMenu jMenu1;
    private javax.swing.JMenu jMenu2;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JMenuItem jMenuItem1;
    private javax.swing.JMenuItem jMenuItem2;
}
但是,每次按下Alt键都不会触发任何ChangeEvents。我不能 找出原因

  • ChangeListener从SelectionModel、鼠标或按键事件触发事件,这些事件是预期的

  • 您可以从ChangeListener模拟事件,例如在菜单上重置选择(将其改为将焦点移动到JTextField)

  • 还有可访问的其他侦听器,它们可以正确地触发自己的事件


看起来菜单栏一旦被选中,就永远不会取消选中。不确定这是不是一个bug

直接收听MenuSelectionManager可能是一个更好的主意,因为在那里,您可以随时收到有关菜单选择的所有更改的通知。需要一些逻辑来过滤掉与菜单栏无关的内容,类似于:

ChangeListener listener = new ChangeListener() {
    @Override
    public void stateChanged(ChangeEvent e) {
        MenuElement[] elements = MenuSelectionManager.defaultManager().getSelectedPath();
        jMenuBar1.setVisible(elements.length > 0 && elements[0] == jMenuBar1);
    }
};
MenuSelectionManager.defaultManager().addChangeListener(listener);
更新

隐藏菜单栏的一个严重缺点是,菜单项的加速器停止工作。原因是,只要求显示的组件的componentInputMaps来处理它们。这是在swing包的内部深处完成的,即通过包私有类KeyboardManager完成的。无法挂接自定义管理器(可以实现自定义管理器来处理未显示的菜单栏)

然而,在链条的另一端,我们可以干涉。基本上有两个选项,都是菜单栏的子类:

  • (非常肮脏的把戏!)覆盖显示总是返回true。我已经看过了,但不能真正推荐,因为可能有副作用,我不知道
  • 一个略显肮脏的技巧:添加一个隐藏的属性,并实现getPreferredSize以在隐藏时返回0高度。肮脏是它依赖于根面板布局,尊重优先高度
修订后的ChangeListener:

bar.setHidden(true);
ChangeListener listener = new ChangeListener() {
    @Override
    public void stateChanged(ChangeEvent e) {
        MenuElement[] elements = MenuSelectionManager.defaultManager().getSelectedPath();
        bar.setHidden(!(elements.length >0 && elements[0] == bar));
    }
};
MenuSelectionManager.defaultManager().addChangeListener(listener);
自定义菜单栏:

public static class JHideableMenuBar extends JMenuBar {

    private boolean hidden;

    public void setHidden(boolean hidden) {
        if (this.hidden == hidden) return;
        this.hidden = hidden;
        revalidate();
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension pref = super.getPreferredSize();
        if (hidden) {
            pref.height = 0;
        }
        return pref;
    }

}

我没有得到指定的行为。嗯。。。或者我不明白你的意思?@kleopatra 1。我没有回答什么问题,有什么答案吗。玩可接受的事件3。模拟结果表明,JMenuBar总是从API中实现的方法返回非_期望值,可能是bug,可能是feature,4。只有它的子级(例如JMenu)可以这样做,jMenuBar1.setVisible(jMenu1.isSelected());可从ButtonModel EDIT访问(已启用,其余事件可从MenuListener访问),5。我必须测试PropertyChangeListener中的事件,@kleopatra PropertyChangeListener-->可能是,但由于woodoo被包装成布尔值(PropertyChangeListener触发相反的事件,并且在启动时永远看不到JMenuBar)@mKorbel,很抱歉,我在理解您时遇到了很多困难。非常好,这很有效。我自己永远也找不到
MenuSelectionManager
。但是,我仍然想知道为什么直接从
JMenuBar
上选择管理器上的
ChangeListener
不起作用。但它起作用了,所以我很高兴!谢谢。由于某种原因,这种方法会禁用菜单项加速器。尽管
setAccelerator
的文档明确指出:“请注意,键入键盘加速器时,无论菜单当前是否显示,它都会工作。”但现在不是这样……如果添加以下代码:
jMenuItem2.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F,java.awt.event.InputEvent.CTRL_MASK));jMenuItem2.setText(“查找”);jMenuItem2.addActionListener(新java.awt.event.ActionListener(){public void actionPerformed(java.awt.event.ActionEvent evt){String what=JOptionPane.showInputDialog(MenuBarTest.this),“搜索什么?”;System.out.println(what);}});
到上面的SSCE加速器设置为ctrl+f,只有在菜单栏可见时才有效。嗯……你是说如果菜单栏不可见,加速器键不起作用吗?不幸的是,这是另一个隐藏问题(很抱歉,我需要想出一个解决办法)
public static class JHideableMenuBar extends JMenuBar {

    private boolean hidden;

    public void setHidden(boolean hidden) {
        if (this.hidden == hidden) return;
        this.hidden = hidden;
        revalidate();
    }

    @Override
    public Dimension getPreferredSize() {
        Dimension pref = super.getPreferredSize();
        if (hidden) {
            pref.height = 0;
        }
        return pref;
    }

}