In my application, I have a JPopupMenu that displays a set of sub-menus:
private static JMenu createMenu(String title) {
JMenu menu = new JMenu(title);
menu.setDelay(2000);
menu.add(new JMenuItem("123"));
menu.add(new JMenuItem("234"));
menu.add(new JMenuItem("345"));
return menu;
}
public static void main(String[] args) {
JFrame frame = new JFrame("Hello");
final JButton button = new JButton("Test");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JPopupMenu jpm = new JPopupMenu();
jpm.add(createMenu("XXX"));
jpm.add(createMenu("YYY"));
jpm.add(createMenu("ZZZ"));
jpm.show(button, 0, 0);
}
});
frame.add(button);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
}
This application usually runs on Windows.
I first mouse-over the first XXX sub-menu. Sometimes, I accidentally move my mouse over YYY, which then causes the first sub-menu to disappear immediately.
From reading the Javadoc, it would seem that calling JMenu.setDelay(2000) should suggest that the JMenu's popup menu wait 2 seconds before popping down. However, it only seems to delay the next menu 2 seconds before popping up.
Is there a way to delay the pop down?
Update
Here's something interesting; from Bug #6563939 Delay should be respected before hiding a JMenu, open since 2007
Javadoc might be misleading in this
point. It says "before submenus are
popped up or down". "Popping down"
could be understood as "hiding", but
it rather has the meaning of "submenu
is rendered below his parent menu".
It also seems to suggest that this depends on the L&F you're using, so it looks like more like something you can only request or hint at:
Each look and feel (L&F) may determine
it's own policy for observing the
delay property. In most cases, the
delay is not observed for top level
menus or while dragging. This method
is a property of the look and feel
code and is used to manage the
idiosyncracies of the various UI
implementations.
Related
On OS X, it makes sense to remove the JMenuBar from your main JFrame and use the screen menu bar instead.
I was under the impression that in recent versions of the Apple JDK this is done using the
Application.getApplication().setDefaultMenuBar( JMenuBar );
call.
When using this, it seems like the accelerator keys are no longer working.
The following program illustrates the problem on OS X:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import com.apple.eawt.Application;
public class MacMenuBarShortcutTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
showUI();
}
});
}
private static void showUI(){
JFrame testFrame = new JFrame("TestFrame");
JLabel content = new JLabel("Press cmd-t to test whether the action is triggered");
testFrame.getContentPane().add(content);
JMenuBar menuBar = new JMenuBar();
Action action = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "It works!");
}
};
action.putValue(Action.NAME, "Test action");
action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(action));
menuBar.add(menu);
Application.getApplication().setDefaultMenuBar(menuBar);
testFrame.setVisible(true);
testFrame.pack();
testFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
The program should allow you to press cmd+t and a dialog will pop up, confirming the shortcut for the action works.
This is however not working. The menu item gets highlighted when pressing the short cut, but the action is not executed.
I did not found any relevant methods on the com.apple.eawt.Application class that I forgot to call. Going over the code in a rather outdated article on the Apple website suggested that shortcuts should be working though.
Note that when using
System.setProperty("apple.laf.useScreenMenuBar", "true")
instead of the Application#setDefaultMenuBar method, it works as expected. Does this mean the system property is still the recommended way, and I shouldn't be using the method on the Application class ?
I was mixing two different things:
The system property
System.setProperty("apple.laf.useScreenMenuBar", "true")
determines whether the Aqua look and feel uses the menubar of your top-level window (e.g. a JFrame) as application menu bar.
Setting this to true will ensure that when you call e.g.
JFrame frame = ...;
JMenuBar menu = ...;
frame.setJMenuBar(menu);
the menu bar will be used as application menu bar when the frame has focus, and that the menu bar will never be painted as part of the JFrame.
When another frame (or top level component) gains focus, the application menu bar will be updated to show the menu bar of that component.
For example when you show a modal dialog using one of the JOptionPane.showXXX methods, the application menu bar will try to show the menu bar of that modal dialog. As this is null, the application menu bar will become empty as long as that modal dialog is not closed.
This is where the Application.setDefaultMenuBar method comes into play. If you call this method with a non-null menu bar, that menu bar will be shown as application menu bar when a top level component without menu bar gains focus.
As such, the snippet in the question has 2 problems:
It does not set the system property to true
It only sets the default menu bar, and forgets to set the menu bar on the JFrame. Because the JFrame itself has no menu bar, the default menu bar is shown which gave the impression that the setDefaultMenuBar method was an alternative for the system property. But this is clearly not the case.
The result is that the shortcuts are not working because the JMenuBar is not contained in the JFrame.
I'm trying to set some text to be displayed where the accelerator binding is usually displayed, for a JMenuItem.
The demarcated Ctrl+Z text in the following image is an example of what I'm trying to set, for another JMenuItem.
I don't actually want to set an accelerator for this JMenuItem, though.
I've poked around the source for several classes, like JMenuItem and BasicMenuItemUI, to no avail.
What's the simplest way to achieve this?
Thanks in advance :)
I assume the reason you want this is so you can prevent the menu from triggering the undo action a second time, when the key combination is already bound on a component on the frame, but this shouldn't be necessary. If the component consumes the key event, the menu won't detect it.
Here's an example with a JTextArea to see what I mean:
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JMenuBar menu = new JMenuBar();
frame.setJMenuBar(menu);
JMenu menuEdit = new JMenu("Edit");
menu.add(menuEdit);
JMenuItem menuEditUndo = new JMenuItem("Undo");
menuEdit.add(menuEditUndo);
menuEditUndo.setAccelerator(KeyStroke.getKeyStroke("control Z"));
menuEditUndo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("menu");
}
});
JTextArea textArea = new JTextArea(20, 40);
textArea.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "undo");
textArea.getActionMap().put("undo", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("text");
}
});
frame.add(new JScrollPane(textArea));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Both the text area and the menu item have bound to the same key combo, but pressing Ctrl+Z while the text area has focus prints only "text" and never "menu". I.e., the action does not happen twice. Although this uses a JTextArea, it should be true of any component.
Within a small Java standalone program with a Swing GUI I use JMenuItem with Accelerator - mostly without any problems:
JMenuItem item = new JMenuItem("Connect", 'C');
KeyStroke ks = KeyStroke.getKeyStroke('C', Event.CTRL_MASK);
item.setAccelerator(ks);
item.addActionListener(this);
My problem is that when I disable the item with
item.setEnabled(false);
and enable it later with
item.setEnabled(true);
I cannot use the accelerator any more. The JMenuItem is correctly shown as enabled in the menu and I can click it with the mouse (and my ActionListener is correctly working) but my accelerator isn't working - so I cannot start "Connect" with Ctrl+C any more.
Does anyone of you know what this problem is or how I can avoid it?
Other menu items which accelerators (but without beeing temporarily disabled) are working. When calling
item.getAccelerator();
after calling item.setEnabled(true) I get the formerly set KeyStroke.
It works with the KeyStroke Ctrl+U but not with Ctrl+C. It seems to me that when disabling the menu item the default copy operation is registered again with Ctrl+C and after enabling the menu item again there's no connection between the KeyStroke and the menu item any more.
While trying to build a small copy of my program to demonstrate the problem I got it:
I did two things together - enabled the JMenuItem (with KeyStroke Ctrl+C) AND requested focus for a JTextField.
Here's a small code for a program that does not react on the KeyStroke Ctrl+C, which is connected to a menu item:
public class ProblemDemo extends JFrame implements ActionListener {
public ProblemDemo() {
super("ProblemDemo");
setSize(500,500);
setLocation(500,300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JMenuBar menubar = new JMenuBar();
JMenuItem menuItem = new JMenuItem("JMenuItem", 'C');
menuItem.setAccelerator(KeyStroke.getKeyStroke('C', Event.CTRL_MASK));
menuItem.addActionListener(this);
JMenu menu = new JMenu("Actions");
menu.add(menuItem);
menubar.add(menu);
setJMenuBar(menubar);
JTextArea textarea = new JTextArea();
getContentPane().setLayout(new BorderLayout());
getContentPane().add(textarea, BorderLayout.CENTER);
setVisible(true);
}
#Override
public void actionPerformed(ActionEvent event) {
if ("JMenuItem".equals(event.getActionCommand())) {
System.out.println("JMenuItem clicked");
}
}
public static void main(String[] args) {
new ProblemDemo();
}
}
I had the same problem trying to enable the copy/cut menu items (as well as buttons in a toolbar), only if something is selected in a JTable by calling setEnabled (true) in a ListSelectionListener.
I solve my problem by calling requestFocusInWindow for the JMenuBar containing the items each time setEnable (true) is called.
Seems that selecting something in the table directs Ctrl+C / Ctrl+x to the table...
May be it'll help
Try to create the following method:
private KeyStroke getNewKeyStroke(){
KeyStroke ks = KeyStroke.getKeyStroke('C', Event.CTRL_MASK);
return KeyStroke;
}
then you can call the following method like that:
item.setEnabled(true);
item.setAccelerator(this.getNewKeyStroke());
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'm trying to make a GUI in java, but JMenuBar has been giving me a hard time for two days. Can someone please tell me why it isn't showing up?
import java.awt.*;
import javax.swing.*;
import javax.swing.JPanel;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.Arrays;
import javax.imageio.ImageIO;
public class selectionFrame extends JFrame
{
Font name;
Font title;
public void setup() //can't use constructer because this isn't given a size until after it is constructed.
{
//getContentPane().add(menuBar);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new FlowLayout());
//getContentPane().add(j);
setJMenuBar(createMenuBar());
//getContentPane().add(createMenuBar());
}
public JMenuBar createMenuBar()
{
JMenuBar menuBar;
JMenu m_file;
JMenuItem mi_save;
JMenuItem mi_load;
JMenu m_edit;
JMenuItem mi_tileHeight;
JMenuItem mi_tileWidth;
menuBar = new JMenuBar();
m_file = new JMenu("File");
m_edit = new JMenu("Edit");
mi_save = new JMenuItem("Save file", KeyEvent.VK_S);
mi_load = new JMenuItem("Load file", KeyEvent.VK_L);
mi_tileHeight = new JMenuItem("Set tile height", KeyEvent.VK_H);
mi_tileWidth = new JMenuItem("Set tile width", KeyEvent.VK_W);
menuBar.add(m_file);
m_file.add(mi_save);
m_file.add(mi_load);
menuBar.add(m_edit);
m_edit.add(mi_tileHeight);
m_edit.add(mi_tileWidth);
return menuBar;
}
public static void main(String[] args) //run
{
selectionFrame sel = new selectionFrame();
sel.setLocationRelativeTo(null);
sel.setSize((int) 400 + (sel.getInsets().left + sel.getInsets().right),(int) 400 + (sel.getInsets().top + sel.getInsets().bottom));
sel.setVisible(true);
sel.setTitle("Tiles/Meta");
sel.setResizable(false);
sel.setFocusable(true);
sel.getContentPane().setSize(sel.getSize());
sel.setLocation((int) sel.getX() - (sel.getWidth()/2),(int) sel.getY() - (sel.getHeight()/2));
sel.setup();
sel.repaint();
}
}
You have an awful lot of extra code there.
public class SelectionFrame extends JFrame
{
Font name;
Font title;
public SelectionFrame()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setJMenuBar(createMenuBar());
}
public JMenuBar createMenuBar()
{
JMenuBar menuBar;
JMenu m_file;
JMenuItem mi_save;
JMenuItem mi_load;
JMenu m_edit;
JMenuItem mi_tileHeight;
JMenuItem mi_tileWidth;
menuBar = new JMenuBar();
m_file = new JMenu("File");
m_edit = new JMenu("Edit");
mi_save = new JMenuItem("Save file", KeyEvent.VK_S);
mi_load = new JMenuItem("Load file", KeyEvent.VK_L);
mi_tileHeight = new JMenuItem("Set tile height",
KeyEvent.VK_H);
mi_tileWidth = new JMenuItem("Set tile width",
KeyEvent.VK_W);
menuBar.add(m_file);
m_file.add(mi_save);
m_file.add(mi_load);
menuBar.add(m_edit);
m_edit.add(mi_tileHeight);
m_edit.add(mi_tileWidth);
return menuBar;
}
public void main( String args[] )
{
SelectionFrame sel = new SelectionFrame();
sel.setLocationRelativeTo(null);
sel.setSize(400 + (sel.getInsets().left + > sel.getInsets().right), 400
+ (sel.getInsets().top + sel.getInsets().bottom));
sel.setTitle("Tiles/Meta");
sel.setResizable(false);
sel.setFocusable(true);
sel.getContentPane().add( new JLabel( "Content", SwingConstants.CENTER),
BorderLayout.CENTER );
sel.setLocation(sel.getX() - (sel.getWidth() / 2), sel.getY() - > (sel.getHeight() / 2));
sel.setVisible(true);
}
}
That shows up with a menu bar and everything. if you add your content to the CENTER of the content pane (by default a border layout), the center automatically fills the whole content area, you don't have to resize anything.
This shows up as a window with a menu bar and everything works fine.
What platform are you doing this on? I'm on Vista, i get what i expect to see.
What Java version are you using? Your menu bar shows up fine in 1.6.0_10 on my system. Try wrapping the body of your main method in an invokeLater() call so that it runs on the correct thread, like so:
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
selectionFrame sel = new selectionFrame();
sel.setLocationRelativeTo(null);
sel.setSize((int) 400 + (sel.getInsets().left + sel.getInsets().right),
(int) 400 + (sel.getInsets().top + sel.getInsets().bottom));
sel.setTitle("Tiles/Meta");
sel.setResizable(false);
sel.setFocusable(true);
sel.getContentPane().setSize(sel.getSize());
sel.setLocation((int) sel.getX() - (sel.getWidth() / 2),
(int) sel.getY() - (sel.getHeight() / 2));
sel.setup();
sel.setVisible(true); // Follow Kendrick's advice too.
}
});
}
Turns out you HAVE to set the JMenuBar inside the JFrame's constructor. I figured this out while looking at the differences between my code, and the marked answers code.
Thank you for your wonderful answer, John Gardner. Without you I would have most likely been stuck for months.
In my case I have tracked down a missing menu bar to a bug where I set the RootPane layout, tsk tsk. The RootPane (see eg http://download.java.net/jdk7/archive/b123/docs/api/javax/swing/JRootPane.html ) controls the layout of the menu bar, so when I changed its layout manager it lost the bar.
Instead, one should use the ContentPane to layout and add components to, eg:
frame.getContentPane().setLayout(...);
frame.getContentPane().add(...);
For future reference... this has nothing to do with the component being visible (as the OP said, the frame is visible but the menu bar is not), I have working code that sets the JMenuBar outside the constructor, and while being swing-thread-safe is Good Practice, it is not the cause of the problem.
sel.setVisible(true);
Should be the last thing you call......
Also, just before the call to sel.setVisible(true); pls invoke sel.pack();
Pls note that instead of setSize it is better to use setPreferredSize, which is leveraged during frame packing.
Not directly relevant to your question, but still -- the use of a good layout manager is a huge time and frustration saviour when using Swing. MigLayout is simply an excellent one-stop layout manager.
When I compile and run it, it shows up with a menu bar with file and edit menu items. Were you expecting more?
Also, capitalize your class- SelectionFrame
EDIT:
One thing I forgot to look at, your code and every answer here is technically wrong. Often, it happens to work, but you are not allowed to do anything with Swing components unless you are in the AWT worker thread.
Normally you don't have to think about the worker thread much because every event that comes from your window will be on the worker thread anyway, but there is a tendency to forget about it when you construct your initial frame--and more often than not it just works anyway.
Sun used to recommend that you can work with components outside the AWT thread until the window has been realized (with either pack() or setVisible(true)) but this is no longer considered safe.
The easiest way to fix this might be for your main to create a swing worker thread before newing your SelectionFrame.
There is only a 50-50 chance this will fix your problem, but you should still take it into consideration whenever working on a GUI.