【问题标题】:JMenuBar SelectionModel ChangeListener only fires onceJMenuBar SelectionModel ChangeListener 只触发一次
【发布时间】:2013-08-07 02:45:51
【问题描述】:

我试图让我的JMenuBar 来模拟 Firefox 和 iTunes 的菜单栏的行为。行为:菜单栏最初是隐藏的。但是,当您按下Alt 时,会出现菜单栏(选中第一个项目),当您没有选择菜单项时,菜单栏会消失。我的想法是通过ChangeListener 在其SelectionModel 上监听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;
}

【问题讨论】:

  • 我没有得到指定的行为。

标签: java swing jmenuitem jmenubar changelistener


【解决方案1】:

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

直接收听 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);

更新

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

不过,在链条的另一端,我们可以进行干预。基本上有两个选项,都是子类菜单栏:

  • (非常肮脏的把戏!)覆盖 isShowing 以始终返回 true。我已经看到这样做了,但不能真正推荐,因为可能有我不知道的副作用
  • 一个有点脏的技巧:添加一个属性 hidden 并实现 getPreferredSize 以在隐藏时返回 0 高度。肮脏是它对 RootPaneLayout 尊重首选项高度的依赖...

修改后的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;
    }

}

【讨论】:

  • 很好,很有效。我永远不会自己找到MenuSelectionManager。不过,我仍然想知道为什么直接来自JMenuBar 的SelectionManager 上的ChangeListener 不起作用。但它有效,所以我很高兴!谢谢。
  • 由于某种原因,这种方法禁用了菜单项加速器。尽管setAccelerator 的文档明确表示:“请注意,当键入键盘加速器时,无论当前是否显示菜单,它都会起作用。”但现在情况并非如此......
  • 如果在上面的SSCCE中加入这个代码:jMenuItem2.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F, java.awt.event.InputEvent.CTRL_MASK)); jMenuItem2.setText("Find"); jMenuItem2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { String what = JOptionPane.showInputDialog(MenuBarTest.this, "Search for what?"); System.out.println(what); } });
  • 快捷键设置为ctrl+f,仅在菜单栏可见时有效。
  • hmm ..如果菜单栏不可见,您是说加速键不起作用吗?不幸的是,这是一个不同的隐藏问题(对不起,在 IDE 上想了一个解决方案)
【解决方案2】:

但是,随后每次按下 Alt 都不会触发 ChangeEvents。我不能 找出原因...

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

  • 您可以模拟来自 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);
            }
        });
    }
}

【讨论】:

  • 嗯...或者我不明白你的建议?
  • @kleopatra 1. 我没有回答任何问题,有任何答案 2. 玩可访问事件 3. JMenuBar 总是从 API 中实现的方法返回非预期值的模拟,可能是错误,可能是功能, 4. 只有它的孩子JMenu 可以做到这一点, jMenuBar1.setVisible(jMenu1.isSelected());可从 ButtonModel EDIT 访问(isEnabled,其余事件可从 MenuListener 访问),5. 我必须测试来自 PropertyChangeListener 的事件,
  • @kleopatra PropertyChangeListener ---> 可以,但是将 woodoo 包装成布尔值(PropertyChangeListener 触发相反的事件并且 JMenuBar 在启动时永远不可见)
  • @mKorbel,很抱歉,我很难理解你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-23
  • 2021-03-22
  • 2015-09-07
  • 2016-11-10
  • 2016-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多