JMenuBar SelectionModel ChangeListener仅触发一次

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

但是,附加的SSCCE的行为并不理想。 框架加载时, 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; } 

一旦被选中,看起来菜鸟从未被选中。 不确定这是不是一个错误。

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

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

更新

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

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

  • (非常脏的技巧!)覆盖isShowing总是返回true。 我已经看到了这一点,但不能真正推荐,因为可能有我不知道的副作用
  • 一个稍微脏的技巧:添加一个隐藏的属性并实现getPreferredSize,如果隐藏则返回0高度。 肮脏是它依赖RootPaneLayout尊重pref高度……

修订后的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); 

自定义menuBar:

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

但是,每次后续的Alt按下都不会触发ChangeEvents。 我无法弄清楚为什么……

  • ChangeListener从SelectionModel,Mouse或Key事件触发事件,这些事件是预期的

  • 你可以模拟来自ChangeListener的事件,例如菜单上的重置选择(把它放在将焦点移动到JTextField上)

  • 可以访问另一个侦听器,正确地触发自己的事件

看到

 import java.awt.BorderLayout; import java.awt.Dimension; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.ButtonModel; import javax.swing.JTextField; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; public class MenuBarTest extends javax.swing.JFrame { 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; private JTextField text = new JTextField("text", 10); public MenuBarTest() { jMenuBar1 = new javax.swing.JMenuBar(); jMenu1 = new javax.swing.JMenu(); jMenu1.addMenuListener(new MenuListener() { @Override public void menuSelected(MenuEvent e) { System.out.println("MenuListener - Selected: " + e.toString()); } @Override public void menuDeselected(MenuEvent e) { System.out.println("MenuListener - Deselected: " + e.toString()); } @Override public void menuCanceled(MenuEvent e) { System.out.println("MenuListener - Canceled: " + e.toString()); } }); jMenu1.getModel().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { ButtonModel model = (ButtonModel) e.getSource(); if (model.isArmed()) { System.out.println("ButtonModel - Armed: " + e.toString()); } else if (model.isEnabled()) { System.out.println("ButtonModel - Enabled: " + e.toString()); } else if (model.isPressed()) { System.out.println("ButtonModel - Pressed: " + e.toString()); } else if (model.isRollover()) { System.out.println("ButtonModel - Rollover: " + e.toString()); } else if (model.isSelected()) { System.out.println("ButtonModel - Selected: " + e.toString()); } else { System.out.println("ButtonModel - !!!!!????: " + e.toString()); } } }); jMenuItem1 = new javax.swing.JMenuItem(); jMenu2 = new javax.swing.JMenu(); jMenuItem2 = new javax.swing.JMenuItem(); jMenu1.setText("File"); jMenuItem1.setText("jMenuItem1"); jMenu1.add(jMenuItem1); jMenuBar1.add(jMenu1); jMenu2.setText("Edit"); jMenuItem2.setText("jMenuItem2"); jMenu2.add(jMenuItem2); jMenuBar1.add(jMenu2); jMenuBar1.setVisible(false); jMenuBar1.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String strPropertyName = evt.getPropertyName(); System.out.println("PropertyChangeListener - NewValue: " + evt.getNewValue()); System.out.println("PropertyChangeListener - OldValue: " + evt.getOldValue()); System.out.println("PropertyChangeListener - PropagationId: " + evt.getPropagationId()); System.out.println("PropertyChangeListener - PropertyName: " + evt.getPropertyName()); if ("MENU.MP_BARBACKGROUND".equals(strPropertyName)) { System.out.println("PropertyChangeListener - MENU.MP_BARBACKGROUND: " + evt.getNewValue()); } } }); jMenuBar1.getSelectionModel().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { System.out.println("ChangeListener - " + e.toString()); jMenuBar1.setVisible(jMenuBar1.isSelected()); System.out.println("ChangeListener - " + jMenuBar1.isSelected()); System.out.println("ChangeListener - " + jMenuBar1.getSelectionModel().isSelected()); java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { text.grabFocus(); text.requestFocusInWindow(); text.setText(text.getText()); text.selectAll(); } }); } }); setJMenuBar(jMenuBar1); add(text, BorderLayout.NORTH); add(new JTextField("text", 10), BorderLayout.SOUTH); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setPreferredSize(new Dimension(400, 300)); 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() { @Override public void run() { new MenuBarTest().setVisible(true); } }); } }