My Program has a JMenuBar with JMenuItems.
They have a ActionListener, and I set a Shortcut with setAccelerator.
Now I am hiding the menu bar when the window become unfocused, to get more space for a displayed image.
But after the first hiding of the menubar, the hotkeys just stop working.
How can I fix that?
I created a little example code to illustrate that strange behavior:
import javax.swing.*;
import java.awt.event.*;
class Example extends JFrame{
public static void main(String[] args) {
new Example(); //main is static
}
static JMenuBar menubar; //be accessable for the ActionListener
Example() {
//JPanel
this.setSize(50,50);
this.setVisible(true);
//Menubar, static
menubar = new JMenuBar();
this.setJMenuBar(menubar);
//Menu
JMenu filemenu = new JMenu("File");
menubar.add(filemenu);
//Item
JMenuItem menuitem = new JMenuItem("Do Something...");
filemenu.add(menuitem);
menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.SHIFT_DOWN_MASK)); // Shift + D
menuitem.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action!");
}
});
JButton button = new JButton("Show/Hide menubar");
this.add(button);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Toggle Bar!");
menubar.setVisible(!menubar.isVisible()); //Toggle
}
});
}
}
For reference:
I'm using Java 1.7.0_60-ea (Java 7) on a Mac.
But this error occurs independent of using the Mac native menu bar or the normal java menu bar inside the JFrame.
You could try to add global keybindings. How to add keybindings is explained here.
Here is an example of what you could do:
//Any component that is always visible in the window (like the image)
JComponent c;
//Get input and action map
InputMap imap = c.getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap amap = c.getActionMap();
//Put keybinding and action
imap.put(KeyStroke.getKeyStroke("shift D"), "doSomething");
amap.put("doSomething", anAction);
Note that it only works in the focused window. But should work regardless of the menubar being visible or not.
Related
As in title, I struggle to get my JMenu to resize when programatically adding components. In my application I need JMenu with JCheckBoxes. Whenever I tick one of them, JSlider should appear just below. Below is the code that makes that happen. The problem I have is that when there's lot of sliders visible, items in JMenu get clumped - see image below.
How can I force redraw/resize/expansion of JMenu to perserve original checkboxes/sliders height?
Also note - JMenu stays visible at all times when selecting checkboxes. It closes only when I click outside of it. But after such 'restart' menu grows and problem is no longer present.
Many thanks in advance!
public class Window extends JFrame implements ActionListener {
private JPanel panel;
private JMenuBar menuBar;
private JMenu menu;
private JSlider slider1;
private JCheckBoxMenuItem checkBox1;
private JCheckBoxMenuItem checkBox2;
private JCheckBoxMenuItem checkBox3;
public Window() {
super("Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setMinimumSize(new Dimension(300,300));
panel = new JPanel();
panel.setMinimumSize(new Dimension(300,300));
menuBar = new JMenuBar();
menu = new JMenu("Options");
checkBox1 = new JCheckBoxMenuItem("option 1");
checkBox2 = new JCheckBoxMenuItem("option 2");
checkBox3 = new JCheckBoxMenuItem("option 3");
checkBox1.addActionListener(this);
//prevent JMenu from closing after selecting CheckBox
checkBox1.setUI(new BasicCheckBoxMenuItemUI() {
#Override
protected void doClick(MenuSelectionManager msm) {
checkBox1.doClick(0);
}
});
slider1 = new JSlider();
slider1.setVisible(false);
menu.add(checkBox1);
menu.add(slider1);
menu.add(checkBox2);
menu.add(checkBox3);
menuBar.add(menu);
setJMenuBar(menuBar);
add(panel);
setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(checkBox1)) {
slider1.setVisible(checkBox1.isSelected());
}
}
}
Here's screen from my main application:
Hiding and reshowing the popup menu will cause it to be resized:
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(checkBox1)) {
slider1.setVisible(checkBox1.isSelected());
menu.setPopupMenuVisible(false);
menu.setPopupMenuVisible(true);
}
}
Call revalidate() it is supposed to do the trick, call it on the newly added components. https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Component.html#revalidate()
But i would say a use case like this is not what menus are meant for? why not use a dialog for dynamic components?
For background https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Component.html#revalidate() call it o the newly added component and see https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Component.html#invalidate() and https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Container.html#isValidateRoot()
I have the following code for a JMenuBar (This code has been taken from a free java program call JGuiD and edited for personal purposes)
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Dimension;
import java.awt.Color;
public class GuiDMenuBar extends JMenuBar
{
JMenu m_file,m_edit,m_help;
JMenuItem mi_f_new,mi_f_open,mi_f_save,mi_f_saveas,mi_f_exit;
JMenuItem mi_e_cut,mi_e_copy,mi_e_paste,mi_e_delete;
JMenuItem mi_v_motif,mi_v_java,mi_v_windows,mi_v_nimbus;
JMenuItem mi_h_help,mi_h_about;
JButton m_code;
public GuiDMenuBar()
{
setBorderPainted(true);
makeFileMenu();
makeEditMenu();
makeCodeButton();
makeHelpMenu();
}
void makeFileMenu()
{
m_file = new JMenu("File");
m_file.setMnemonic('F');
mi_f_new = new JMenuItem("New",new ImageIcon("icons/new_project.png"));
mi_f_new.setMnemonic('N');
mi_f_open = new JMenuItem("Open",new ImageIcon("icons/open_project.png"));
mi_f_open.setMnemonic('O');
mi_f_save = new JMenuItem("Save",new ImageIcon("icons/save.png"));
mi_f_save.setMnemonic('S');
mi_f_saveas = new JMenuItem("Save Java File",new ImageIcon("icons/saveas.png"));
mi_f_saveas.setMnemonic('J');
mi_f_exit = new JMenuItem("Exit",new ImageIcon("icons/exit.png"));
mi_f_exit.setMnemonic('X');
mi_f_new.setAccelerator(KeyStroke.getKeyStroke("control N"));
mi_f_open.setAccelerator(KeyStroke.getKeyStroke("control O"));
mi_f_save.setAccelerator(KeyStroke.getKeyStroke("control S"));
mi_f_saveas.setAccelerator(KeyStroke.getKeyStroke("control J"));
mi_f_exit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4,InputEvent.ALT_MASK));
m_file.add(mi_f_new);
m_file.add(mi_f_open);
m_file.addSeparator();
m_file.add(mi_f_save);
m_file.add(mi_f_saveas);
m_file.addSeparator();
m_file.add(mi_f_exit);
add(m_file);
}
void makeEditMenu()
{
m_edit = new JMenu("Edit");
m_edit.setMnemonic('E');
mi_e_cut = new JMenuItem("Cut",new ImageIcon("icons/cut.png"));
mi_e_cut.setMnemonic('X');
mi_e_copy = new JMenuItem("Copy",new ImageIcon("icons/copy.png"));
mi_e_copy.setMnemonic('C');
mi_e_paste = new JMenuItem("Paste",new ImageIcon("icons/paste.png"));
mi_e_paste.setMnemonic('P');
mi_e_delete = new JMenuItem("Delete",new ImageIcon("icons/delete.png"));
mi_e_delete.setMnemonic('D');
mi_e_cut.setAccelerator(KeyStroke.getKeyStroke("control X"));
mi_e_copy.setAccelerator(KeyStroke.getKeyStroke("control C"));
mi_e_paste.setAccelerator(KeyStroke.getKeyStroke("control V"));
mi_e_delete.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0));
m_edit.add(mi_e_cut);
m_edit.add(mi_e_copy);
m_edit.add(mi_e_paste);
m_edit.add(mi_e_delete);
add(m_edit);
}
void makeHelpMenu()
{
m_help = new JMenu("Help");
m_help.setMnemonic('H');
mi_h_help = new JMenuItem("Help",new ImageIcon("icons/help.png"));
mi_h_help.setMnemonic('H');
mi_h_about = new JMenuItem("About");
mi_h_about.setMnemonic('A');
mi_h_help.setAccelerator(KeyStroke.getKeyStroke("F1"));
mi_h_about.setAccelerator(KeyStroke.getKeyStroke("control A"));
m_help.add(mi_h_help);
m_help.addSeparator();
m_help.add(mi_h_about);
add(m_help);
}
void makeCodeButton()
{
m_code = new JButton();
m_code.setOpaque(false);
m_code.setContentAreaFilled(false);
m_code.setBorder(null);
m_code.setFocusable(false);
m_code.setText("Code Shift C");
Dimension dBt = new Dimension(75,25);
m_code.setMinimumSize(dBt);
m_code.setPreferredSize(dBt);
m_code.setMaximumSize(dBt);
m_code.getModel().addChangeListener(new ChangeListener()
{
#Override
public void stateChanged(ChangeEvent e)
{
ButtonModel model = (ButtonModel) e.getSource();
if(model.isRollover())
{
m_code.setBackground(Color.RED);
m_code.setOpaque(true);
}
else
{
m_code.setBackground(null);
m_code.setOpaque(false);
m_code.setContentAreaFilled(false);
}
}
});
m_code.setMnemonic('C');
add(m_code);
}
public void addListeners(ActionListener al)
{
mi_f_new.addActionListener(al);
mi_f_open.addActionListener(al);
mi_f_save.addActionListener(al);
mi_f_saveas.addActionListener(al);
mi_f_exit.addActionListener(al);
mi_e_cut.addActionListener(al);
mi_e_copy.addActionListener(al);
mi_e_paste.addActionListener(al);
mi_e_delete.addActionListener(al);
mi_h_help.addActionListener(al);
mi_h_about.addActionListener(al);
m_code.addActionListener(al);
}
}
My aim is to make the JButton to appear like it is a JMenu. This would entail that the button only changes colour when I am interacting with the rest of the JMenuBar not just when I hover over it. For example if I had clicked on the JMenu m_file and then hovered over the JButton the background would change, however if I was not previously interacting with the JMenuBar it should not change the background when I hover over the JButton. The next thing required would be the JMenuBar treats it as a JMenu. I mean this in the sense that when F10 is clicked and the first JMenu is selected. After which you can then click on the right arrow on the arrow key pad on your keyboard, this will select the next JMenu. However using this method of navigation skips the JButton and does not allow you to interact with the JButton in anyway. I also mean this in the sense that if you interact with a JMenu and then hover over the JButton the JMenuBar shows you are still hovering over the JMenu as well (See Image).
So I suppose I have three questions.
How do you get the JButton to only change colour if the JMenuBar is already being interacted with?
How do you get the JMenuBar to treat the JButton as a JMenu in the senses that I described?
Is anyone aware of the exact colour of the background of the JMenu when you hover over it? Since I would prefer to change my JButton's background to the same colour of the JMenu's background instead of it just being red.
Thanks,
Dan
If a popup menu is still open when another component is clicked, then the component does not get the event, because it's probably consumed by the popup. This happens for all JPopupmenus in general.
This happens only in Java 7 with windows LAF (Windows7). Is there a workaround? Is it a known bug?
import javax.swing.*;
import java.awt.event.*;
public class Test
{
public static void main(String[] s)
throws Exception
{
String lookAnfFeelClassName = UIManager.getSystemLookAndFeelClassName();
UIManager.setLookAndFeel(lookAnfFeelClassName);
JMenu menu = new JMenu("TEST Menu");
JMenuItem menuItem = new JMenuItem("Menu Item 1");
JMenuBar menuBar = new JMenuBar();
menu.add(menuItem);
menuBar.add(menu);
final JButton b = new JButton("Test");
b.setBounds(5, 50, 60, 20);
b.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//If the Menu is open when I press the button, the putton is not pressed
//so I have to press it again.
JOptionPane.showMessageDialog(b, "Button Pressed");
}
}
);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(150, 150);
frame.setJMenuBar(menuBar);
frame.getContentPane().setLayout(null);
frame.getContentPane().add(b);
frame.setVisible(true);
}
}
Here is the magic line that fixes the problem:
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.FALSE);
I found this after looking into the source code for the BasicPopupMenuUI class. Apparently this behaviour is a deliberate design choice according to the following comments in the code, but it sure feels like a bug to me.
// Ask UIManager about should we consume event that closes
// popup. This made to match native apps behaviour.
By the way, it happens in Java 5 and 6 too.
I have 1 JFrame and 10 JPanel components as seperate classes. There is also JMenuBar on jframe. When a menu item clicked, i remove all content of contentPane of jframe (removeAll) and add one of my jpanels.
Here is my code;
// this function changes panel
public static void SwitchPanel(Component comp)
{
Container panel = getContentPane();
panel.removeAll();
panel.add(comp);
panel.revalidate();
panel.repaint();
}
// this function defines menu items and their listeners
public JMenuItem AddMenuItem(JMenu menu, String name, final JPanel toPanel) {
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SwitchPanel(toPanel);
}
});
return menuItem;
}
and i add menu items like that;
AddMenuItem(menu1, "some menu item", new MyPersonalJPanel());
Everything works. BUT i want to create new jpanel when a related menu item clicked. I mean create jpanel if only it is necessary. Current code creates all jpanels first. When i clicked a menu item, shows me jpanel created before.
I think it can be done with Class.forName method, but i couldn't figure it out. Any help?
You should do the new MyPersonalJPanel() in the public void actionPerformed(ActionEvent e) method. That way the panel would be created each time the user click on the menu.
Your code would then be:
// this function defines menu items and their listeners
public JMenuItem AddMenuItem(JMenu menu, String name) {
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SwitchPanel(new MyPersonalJPanel());
}
});
return menuItem;
}
Is there any way to auto-select a jmenu within my jmenubar when the user's pressing the "ALT" key ? (Like windows softwares)
The problem is that the default behavior of my jframe, when the "ALT" key is pressed, is to show up a menu containing the following actions : restore, move, size, reduce, ...
What I want my java application to do, is to select my jmenu first item when "alt" is pressed.
(Like it would do with a mnemonic : "alt + f")
Add the action to the ActionMap and InputMap of your JRootPane. See below:
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class MenuExample {
private void setupMenuKey(final JFrame frame) {
Action menuAction = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
JRootPane rootPane = frame.getRootPane();
JMenuBar jMenuBar = rootPane.getJMenuBar();
JMenu menu = jMenuBar.getMenu(0);
menu.doClick();
}
};
JRootPane rootPane = frame.getRootPane();
ActionMap actionMap = rootPane.getActionMap();
final String MENU_ACTION_KEY = "expand_that_first_menu_please";
actionMap.put(MENU_ACTION_KEY, menuAction);
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true), MENU_ACTION_KEY);
}
private JFrame build() {
JFrame frame = new JFrame("Hello");
frame.setSize(300, 300);
JMenuBar bar = new JMenuBar();
List<String> letters = Arrays.asList("A", "B", "C");
for (int i = 0; i < 3; i++) {
JMenu menu = new JMenu("Menu " + i);
for (String string : letters) {
menu.add(new JMenuItem(String.format("Menu %s - %s", i, string)));
}
bar.add(menu);
}
frame.setJMenuBar(bar);
JButton b = new JButton("click");
JPanel p = new JPanel();
p.add(b);
frame.add(p);
setupMenuKey(frame);
return frame;
}
public static void main(String[] args) {
MenuExample menuExample = new MenuExample();
JFrame frame = menuExample.build();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Use actionmap and inputmap of JComponent to do this job. Since JFrame is not a descendant of JCompoenent i would suggest you adding JPanel to your jframe and then,
Action action = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
jMenu1.doClick();
}
};
jPanel1.getActionMap().put("myAction", action);
jPanel1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ALT, KeyEvent.ALT_DOWN_MASK), "myAction");
I've found the response very useful, but also with a drawback.
Imagine you're using Cross PlatForm L&F (Metal) and you've defined mnemonics to all the menus; you know that those menus are invoked against pressing . So, if we put the solution provided by black panda the results on a pressing are first we watch the desired menu deployed but immediately after switches to the first menu.
I think that the best way would be on ALT pressing only the first menu should be selected but without showing its contents (Those contents shoud appear on a down key pressing). Is there any way to do this in Cross Platform L&F?.
Alternatively there is another way to proceed without any extra code. If we invoke the System L&F (Windows in my case) the ALT key behaves as desired. See stackoverflow question 13474555 for details.
I have found just by using frame.setJMenuBar(jmenubar); where frame is your JFrame and jmenubar is your JMenuBar, it'll automatically do this. You don't even have to add it to your layout manager.