JMenuItem has the following constructor: (Source: GrepCode)
public JMenuItem(Action a) {
this();
setAction(a);
}
However, when my code has
import javax.swing.*;
import java.awt.event.ActionEvent;
public class ActionTest extends JApplet {
private final JFrame frame = new JFrame("Title");
private final JMenuBar menuBar = new JMenuBar();
private final JMenu fileMenu = new JMenu("File");
protected Action someAction;
private JMenuItem someButton = new JMenuItem(someAction);
public ActionTest() {}
#Override
public final void init() {
frame.setJMenuBar(menuBar);
menuBar.add(fileMenu);
fileMenu.add(someButton);
someButton.setText("Button");
someAction = new AbstractAction("Title") {
public void actionPerformed(ActionEvent event) {
//do stuff
}
};
frame.setVisible(true);
}
public static void main(String[] args) {
JApplet applet = new ActionTest();
applet.init();
}
}
and I press the JMenuItem, actionPerformed() is not even called.
Is this a bug, or is my approach completely wrong?
After doing more research, I find that this is the method that it eventually boils down to. It seems to implement a shallow copy, which should simply point to the same memory block that I gave it in the constructor.
The same thing should be occurring when I add the file menu to the menu bar. When the file menu is added, it references the memory block. Whatever is inside that memory block is what is displayed. Then, I add the menu item and it appears in the JMenu.
Somehow it is different when I'm dealing with Actions or constructors. Could somebody explain the difference?
It seems like from what you've posted that you haven't defined your Action when you initialize the JMenuItem. Therefore, because you are passing in null, no action is being triggered
someButton is initialized before someAction, so you are passing null to the JMenuItem. Initialize someButton after you have created someAction and everything will go fine.
Related
I know how to create Java Swing submenus using JMenu. When we hover the mouse over a JMenu object, it displays a JPopupMenu showing the submenu items, like this:
Submenu using JMenu
My problem is that in my application, determining which menu elements will have a submenu is expensive. I don't want to have to determine in advance whether a particular menu element should be a JMenu or just a JMenuItem. I want to make every element a JMenuItem and display a submenu for it only if the user requests it by, e.g., hovering the mouse over a menu item. Like this:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Menu2 extends JFrame
{
public Menu2()
{
super("Menu2");
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu mItems = new JMenu("Items");
menuBar.add(mItems);
mItems.add(new JMI("A"));
mItems.add(new JMI("B"));
mItems.add(new JMI("C"));
JLabel stuff = new JLabel("Other stuff");
stuff.setPreferredSize(new Dimension(200,200));
getContentPane().add(stuff);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
private class JMI extends JMenuItem
implements MouseListener
{
public JPopupMenu childrenPopup = null;
public JMI(String label)
{
super(label);
addMouseListener(this);
}
// MouseListener
public void mouseClicked(MouseEvent ev) {}
public void mouseEntered(MouseEvent ev)
{
// Show a submenu for item "B" only.
// In real life we'd want a Timer to delay showing the submenu
// until we are sure the user is hovering the mouse.
// For simplicity I've omitted it.
if (getText().equals("B")) {
if (childrenPopup == null) {
childrenPopup = new JPopupMenu();
// Expensive processing to determine submenu elements...
childrenPopup.add("D");
childrenPopup.add("E");
}
// Display the submenu
childrenPopup.show(this,getWidth(),0);
}
}
public void mouseExited(MouseEvent ev) {}
public void mousePressed(MouseEvent ev) {}
public void mouseReleased(MouseEvent ev) {}
}
public static void main(String[] args)
throws Exception
{
new Menu2().setVisible(true);
}
}
The only problem is that when my manually created JPopupMenu is displayed, the rest of the menu gets closed. The resulting display does not look like the earlier one, but rather like this:
Submenu displayed manually
Note that I did not click on the "B" menu item, only moved the mouse into it. The menu did not close due to a mouse click.
How can I do what JMenu does -- display a JPopupMenu without closing the rest of the menu?
The approach I've tentatively decided upon is to extend JMenu instead
of JMenuItem and use this type for all of my menu elements. But I
won't populate these elements (the expensive step) until the user
requests it by hovering the mouse.
To avoid cluttering up the menu with the arrow icons that JMenu
normally displays (potentially misleading in this case), I use a technique described by Stackoverflow's MadProgrammer to instantiate an arrowless JMenu in a static factory method. Since I
restore the arrow icon property after creating the arrowless JMenu,
normal JMenu instances created elsewhere will still show the arrow.
Some menu elements will need to execute actions and close the menu,
like a JMenuItem does. A JMenu doesn't normally respond to mouse
clicks, so I execute click actions in my MouseListener.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class Menu3 extends JFrame
{
public Menu3()
{
super("Menu3");
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu mItems = new JMenu("Items");
menuBar.add(mItems);
mItems.add(JM.create("A"));
mItems.add(JM.create("B"));
mItems.add(JM.create("C"));
JLabel stuff = new JLabel("Other stuff");
stuff.setPreferredSize(new Dimension(200,200));
getContentPane().add(stuff);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
private static class JM extends JMenu
implements MouseListener
{
private static final String ARROW_ICON_KEY = "Menu.arrowIcon";
private boolean populated = false; // Submenu already populated?
protected JM(String label)
{
super(label);
addMouseListener(this);
}
// This static factory method returns a JM without an arrow icon.
public static JM create(String label)
{
UIDefaults uiDefaults = UIManager.getLookAndFeelDefaults();
Object savedArrowIcon = uiDefaults.get(ARROW_ICON_KEY);
uiDefaults.put(ARROW_ICON_KEY,null);
JM newJM = new JM(label);
uiDefaults.put(ARROW_ICON_KEY,savedArrowIcon);
return newJM;
}
// MouseListener
public void mouseClicked(MouseEvent ev)
{
// Since some menu elements need to execute actions and a JMenu
// doesn't normally respond to mouse clicks, we execute click
// actions here. In real life we'll probably fire some event
// for which an EventListener can listen. For illustrative
// purposes we'll just write out a trace message.
System.err.println("Executing "+getText());
MenuSelectionManager.defaultManager().clearSelectedPath();
}
public void mouseEntered(MouseEvent ev)
{
// In real life we'd want a Timer to delay showing the submenu
// until we are sure the user is "hovering" the mouse.
// For simplicity I've omitted it.
// Populate this submenu only once
if (!populated) {
// For purposes of example, show a submenu for item "B" only.
if (getText().equals("B")) {
// Expensive processing...
add(create("D"));
add(create("E"));
}
populated = true;
}
}
public void mouseExited(MouseEvent ev) {}
public void mousePressed(MouseEvent ev) {}
public void mouseReleased(MouseEvent ev) {}
}
public static void main(String[] args)
throws Exception
{
new Menu3().setVisible(true);
}
}
The result works the way I want:
Menu3 with open menu
I am already listening to MenuListener.menuSelected() to know when a JMenu is showing on the screen. Initially, the JMenu has 1 JMenuItem which says "Loading...". After a slow operation, I add JMenuItems to the JMenu. The JMenu continues to show "Loading...". If I select another JMenu and come back, then the JMenu shows the added JMenuItems. How do I cause the added JMenuItems to show up immediately?
Here's the code which reproduces what is happening.
public class AddMenuItem extends JFrame
{
private final JMenu m_menu = new JMenu("Edit");
public static void main(String args[])
{
new AddMenuItem().
setVisible(true);
}
public AddMenuItem()
{
JMenuBar bar;
bar = new JMenuBar();
bar.add(m_menu);
setJMenuBar(bar);
setSize(600, 400);
m_menu.addMenuListener(new MenuListener()
{
#Override
public void menuSelected(MenuEvent e)
{
JMenuItem loading;
loading = new JMenuItem("Loading...");
loading.setEnabled(false);
m_menu.removeAll();
m_menu.add(loading);
// This represents a long running task which updates the menu afterwards
new Timer(5 * 1000, event -> updateMenu()).start();
}
#Override
public void menuDeselected(MenuEvent e)
{
// nothing to do
}
#Override
public void menuCanceled(MenuEvent e)
{
// nothing to do
}
});
}
private void updateMenu()
{
m_menu.removeAll();
m_menu.add(new JMenuItem("1"));
m_menu.add(new JMenuItem("2"));
m_menu.add(new JMenuItem("3"));
m_menu.revalidate();
}
}
It seems that this code example does exactly what I want but I am not sure what they are doing to make the visible menu repaint on the screen.
Initially, the JMenu has 1 JMenuItem which says "Loading...".
Code invoked from a listener executes on the Event Dispatch Thread (EDT). When you execute a long running task, it prevents the EDT from responding to events and repainting the GUI.
So to solve the problem the task started by that menu items needs to execute in a separate Thread. You might want to consider using a SwingWorker. Read the section from the Swing tutorial on Concurrency for more information about the EDT and SwingWorker.
Edit:
The problem is that once I know what to add to the menu, I don't know how to get the menu to show the added JMenuItems
You get the JMenu from the MenuEvent of your MenuListener:
JMenu menu = (JMenu)e.getSource();
menu.add( new JMenuItem( "Loading..." ) );
Edit:
Based on the SSCCE you can do something like:
private void updateMenu()
{
m_menu.removeAll();
m_menu.add(new JMenuItem("1"));
m_menu.add(new JMenuItem("2"));
m_menu.add(new JMenuItem("3"));
JPopupMenu popup = m_menu.getPopupMenu();
popup.pack();
}
But remember because your long running task executes in a separate Thread, you would need to invoke the above code using a SwingUtiltities.invokeLater so that the code is executed on the EDT.
It seems that this code example does exactly what I want but I am not sure what they are doing to make the visible menu repaint on the screen.
I think they are just hiding and showing the menu again.
Instead of doing m_menu.revalidate(), do the following...
if (m_menu.isPopupMenuVisible())
{
m_menu.setPopupMenuVisible(false);
m_menu.setPopupMenuVisible(true);
}
I'm trying to make a separate class for an action listener but I'm not sure how to add the action listener to the menu item. I've been trying a few different things but none of them are letting the message dialog appear. I have the action listener in a separate class and the menu item in a separate class and I'm trying to get them to work together.
public class HangmanView {
Listener listener = new Listener();
public JMenuItem getMenuItem() {
JMenuItem menuItem = new JMenuItem("Developer", KeyEvent.VK_T);
menuItem.addActionListener(new Listener());
return menuItem;
}
public JMenuBar menuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("File");
menuBar.add(menu);
menu.add(getMenuItem());
return menuBar;
}
Another Class:
public class Listener {
JFrame dialogFrame = new JFrame();
public JFrame menuItemListener() {
HangmanView hangmanView = new HangmanView();
hangmanView.getMenuItem().addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {// right click key
JOptionPane.showMessageDialog(dialogFrame, "Developer: Joe"
, "Developer",
JOptionPane.INFORMATION_MESSAGE);
}// end actionPerformed method
});
return dialogFrame;
}
}
You seem to be suffering from a lot of confusion as to classes, interfaces, etc so it's actually hard to know where to begin!
First up your Listener class needs to implement ActionListener.
Then you need to add it inside your HangmanView class the same way you already are:
public class Listener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {// right click key
JOptionPane.showMessageDialog(dialogFrame, "Developer: Joe"
, "Developer",
JOptionPane.INFORMATION_MESSAGE);
}// end actionPerformed method
});
And that's it, you are done...
I am trying to design a basic editor type of GUI in Java using Swing. I have a menu item named New clicking on which I want a blank text area to fill up the GUI. My code is as folows :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class UI extends JFrame {
private JMenuBar bar;
private JMenu menu;
private JMenuItem item;
private JTextPane tp;
public UI() {
setLayout(new FlowLayout());
bar = new JMenuBar();
setJMenuBar(bar);
menu = new JMenu("File");
bar.add(menu);
item = new JMenuItem("New");
menu.add(item);
item.addActionListener(new xyz());
}
public class xyz implements ActionListener {
public void actionPerformed(ActionEvent arg0) {
JTextPane tp = new JTextPane();
add(tp);
}
}
public static void main(String args[]) {
// do the rest of the stuffs
}
}
However, even on clicking on the New, I do not get the textPane on the same frame. Can someone please explain.
use JTextPane#setText("") instead of to create a new JTextPane
otherwise you have to notify Container with (re)validate() and repaint()
The text-panes should probably be added to a JTabbedPane if this app. supports multiple documents. If it is intended for 'single document', put the text pane onto the frame at start-up.
I've got a new UI I'm working on implementing in Java and I'm having trouble implementing a JPopupMenu containing a JMenu (as well as several JMenuItems), which itself contains several JMenuItems. The JPopupMenu appears where I click the RMB, and it looks good, but the "Connect" JMenu doesn't seem to have any children when I mouse-over, despite my best efforts to .add() them.
Having looked at several examples online, I haven't seen any that specifically implement a listener for mouseEntered() to roll out the sub-items. I'm of a mind that I'm messing something up in my menu initialization method.
I've attached the pertinent code for your perusal.
//Elsewhere...
private JPopupMenu _clickMenu;
//End Elsehwere...
private void initializeMenu()
{
_clickMenu = new JPopupMenu();
_clickMenu.setVisible(false);
_clickMenu.add(generateConnectionMenu());
JMenuItem menuItem;
menuItem = new JMenuItem("Configure");
addMenuItemListeners(menuItem);
_clickMenu.add(menuItem);
menuItem = new JMenuItem("Status");
addMenuItemListeners(menuItem);
_clickMenu.add(menuItem);
}
private JMenu generateConnectionMenu()
{
JMenu menu = new JMenu("Connect");
List<Port> portList = _database.getAllPortsInCard(_cardId);
for(int i = 0; i < portList.size(); i++)
{
menu.add(new JMenuItem(portList.get(i).getName()));
}
return menu;
}
The code is certainly not the prettiest, but go easy on me as it's been altered too many times today as time permitted while I tried to figure out why this wasn't working. I'm thinking it may be a question of scope, but I've tried a few different code configurations to no avail. Feel free to ask any followup questions or smack me for an obvious oversight (it's happened before...). Thanks all!
Edit:
Chalk this one up to a lack of experience with Java and Swing... I was manually positioning and making the JPopupMenu visible instead of using the JComponent.setComponentPopupMenu(menu) method. After doing this for the card module in the above image (itself a JButton), the submenu displays correctly. A different, functional version of the initialization code is included below.
private void initializeMenu()
{
_cardMenu = new JPopupMenu();
JMenu menu = new JMenu("Connect");
JMenuItem menuItem;
menuItem = new JMenuItem("1");
menu.add(menuItem);
menuItem = new JMenuItem("2");
menu.add(menuItem);
_cardMenu.add(menu);
_cardMenu.add(new JMenuItem("Configure"));
_cardMenu.add(new JMenuItem("Status"));
_mainButton.setComponentPopupMenu(_cardMenu); //Important, apparently!
}
So, lesson learned. Thanks for the help guys!
This is common Bug or Swing property that in one moment can be visible only one Lightweight popup window, same issue is e.g. with popup from JComboBox added into JPopupMenu,
change Lightweight property to the Heavyweight
better would be
use un_decorated JDialog or JOptionPane with JComponents
EDIT #trashgod
everything works as I excepted, all JMenus, JMenuItems are visible and repeatly fired correct evets
code
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ContextMenu implements ActionListener, MenuListener, MenuKeyListener {
private JTextArea textArea = new JTextArea();
public ContextMenu() {
final JPopupMenu contextMenu = new JPopupMenu("Edit");
JMenu menu = new JMenu("Sub Menu");
menu.add(makeMenuItem("Sub Menu Save"));
menu.add(makeMenuItem("Sub Menu Save As"));
menu.add(makeMenuItem("Sub Menu Close"));
menu.addMenuListener(this);
JMenu menu1 = new JMenu("Sub Menu");
menu1.add(makeMenuItem("Deepest Sub Menu Save"));
menu1.add(makeMenuItem("Deepest Sub Menu Save As"));
menu1.add(makeMenuItem("Deepest Sub Menu Close"));
menu.add(menu1);
menu1.addMenuListener(this);
contextMenu.add(menu);
contextMenu.add(makeMenuItem("Plain Save"));
contextMenu.add(makeMenuItem("Plain Save As"));
contextMenu.add(makeMenuItem("Plain Close"));
contextMenu.addMenuKeyListener(this);
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
frame.add(panel);
panel.setComponentPopupMenu(contextMenu);
textArea.setInheritsPopupMenu(true);
panel.add(BorderLayout.CENTER, textArea);
JTextField textField = new JTextField();
textField.setInheritsPopupMenu(true);
panel.add(BorderLayout.SOUTH, textField);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 200);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
textArea.append(e.getActionCommand() + "\n");
}
private JMenuItem makeMenuItem(String label) {
JMenuItem item = new JMenuItem(label);
item.addActionListener(this);
return item;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ContextMenu contextMenu = new ContextMenu();
}
});
}
public void menuSelected(MenuEvent e) {
textArea.append("menuSelected" + "\n");
}
public void menuDeselected(MenuEvent e) {
textArea.append("menuDeselected" + "\n");
}
public void menuCanceled(MenuEvent e) {
textArea.append("menuCanceled" + "\n");
}
public void menuKeyTyped(MenuKeyEvent e) {
textArea.append("menuKeyTyped" + "\n");
}
public void menuKeyPressed(MenuKeyEvent e) {
textArea.append("menuKeyPressed" + "\n");
}
public void menuKeyReleased(MenuKeyEvent e) {
textArea.append("menuKeyReleased" + "\n");
}
}
I don't see an obvious problem in the code shown, although #mKorbel's point may apply. For reference, this ControlPanel adds a subMenu with several items.