Reputation: 13486
I am trying to get my JMenuBar
to model the behavior of the menu bars of Firefox and iTunes. The behavior: the menu bar is initially hidden. But, when you press Alt
, the menu bar appears (with the first item selected) and when you don't have a menu item selected, the menu bar disappears. My idea was to listen for selection changes to the JMenuBar
via a ChangeListener
on its SelectionModel
.
However, the behavior of the attached SSCCE is not as desired. When the frame loads, the JMenuBar
is not visible. When you press Alt
, the menu bar appears with the first menu selected (thanks to the WindowsLookAndFeel
). However, every subsequent Alt
pressed fires no ChangeEvents
. I can't figure out why...
Anyone have light to shed?
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;
}
Upvotes: 2
Views: 752
Reputation: 51525
Looks like the menubar is never unselected, once it had been selected. Not sure if that's a bug or not.
Could be a better idea to listen directly to the MenuSelectionManager as that's where you are notified about all changes to menu selection anywhere. Needs some logic to filter out those unrelated to the menuBar, something similar to:
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);
Update
A hefty drawback of hiding the menubar is that accelerators to its menuItems stop working. The reason is that only componentInputMaps of components which are showing are asked to handle them. This is done deep down in the bowels of the swing package, namely by the package private class KeyboardManager. No way to hook-in a custom manager (which might be implemented to handle menubars that are not showing).
At the other end of the chain, we can interfer, though. Basically two options, both subclassing menubar:
The revised 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);
The custom 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;
}
}
Upvotes: 3
Reputation: 109813
However, every subsequent Alt pressed fires no ChangeEvents. I can't figure out why...
ChangeListener firing events from SelectionModel, Mouse or Key events, those events are expected
you can to simulating events from ChangeListener e.g. reset selection on menu (put that instead on moving Focus to JTextField)
there are accesible another listeners, that firing own events and correctly
see
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);
}
});
}
}
Upvotes: 2