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
Related
I need to get all existing TextFields in an app window so that I can be able to automatically add popup menus to all of them. How do you do that?
The code is below. How come when I call:
JTextFieldRegularPopupMenu.addToAll(jpanel) it works fine and affects all relevant JTextFields. But when I do it with a JDialog from a regular JDialog netbeans class - JTextFieldRegularPopupMenu.addToAll(this), it doesn't work. What could be the problem?
import java.awt.Component;
import java.awt.Container;
import javax.swing.*;
import java.awt.event.ActionEvent;
import javax.swing.JPopupMenu;
import javax.swing.undo.*;
public class JTextFieldRegularPopupMenu {
public static void addToAll(Container frm) {
JTextField txtTmp = null;
Component[] components = frm.getComponents();
for(int i=0;i<components.length;i++){
if( components[i] instanceof JTextField ){
txtTmp = (JTextField)components[i];
addTo(txtTmp);
}
}
}
public static void addTo(JTextField txtField)
{
JPopupMenu popup = new JPopupMenu();
UndoManager undoManager = new UndoManager();
txtField.getDocument().addUndoableEditListener(undoManager);
Action undoAction = new AbstractAction("Undo") {
#Override
public void actionPerformed(ActionEvent ae) {
if (undoManager.canUndo()) {
undoManager.undo();
}
else {
// No Undo
}
}
};
Action copyAction = new AbstractAction("Copy") {
#Override
public void actionPerformed(ActionEvent ae) {
txtField.copy();
}
};
Action cutAction = new AbstractAction("Cut") {
#Override
public void actionPerformed(ActionEvent ae) {
txtField.cut();
}
};
Action pasteAction = new AbstractAction("Paste") {
#Override
public void actionPerformed(ActionEvent ae) {
txtField.paste();
}
};
Action selectAllAction = new AbstractAction("Select All") {
#Override
public void actionPerformed(ActionEvent ae) {
txtField.selectAll();
}
};
cutAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control X"));
copyAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control C"));
pasteAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control V"));
selectAllAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control A"));
popup.add (undoAction);
popup.addSeparator();
popup.add (cutAction);
popup.add (copyAction);
popup.add (pasteAction);
popup.addSeparator();
popup.add (selectAllAction);
txtField.setComponentPopupMenu(popup);
}
}
Netbeans JFrame Class
public class FrmAddNewUser extends javax.swing.JDialog {
/**
* Creates new form FrmAddNewUser
*/
public FrmAddNewUser(java.awt.Frame parent, boolean modal) {
super(parent, modal);
initComponents();
myInitComponents();
}
private void myInitComponents()
{
JTextFieldRegularPopupMenu.AddToAll(this); //this doesn't work... but works on jpanel objects..
}
It sounds like you are trying make a uniform change to all existing JTextFields, correct? This really depends, but storing all of the objects in a data structure like an ArrayList might be your best bet. Then use a for loop to apply the same change at each index.
Well you can't really do that in advance because windows and components are not all created at the start of an application. So you would need to manage this dynamically as an application creates and displays a window.
One way might be to use a KeyboardFocusManager to listen for focus changes.
When focus changes you can then invoke the getPopupMenuComponent() method to get the popup menu of the component. If the menu is null, then you need to add your popup menu.
Check out Global Event Listeners for a simple example using this concepts that shows how to select all the text when a text field gains focus.
So you would need to modify all your applications to add this listener when you start the application.
Edit:
Using your current approach you would want to pass in a Window object to a method. Then you can use the getContentPane() method to get the contain holding the components.
Then you can use a class like Swing Utils. This will do a recursive search of the content pane to find all components. Then you iterate through the List and add your logic.
Then this code will work for both frames, dialogs etc.
Also, you don't need to always create custom Actions. You can use Actions from the DefaultEditorKit. For example:
JMenuItem copy = new JMenuItem( new DefaultEditorKit.CopyAction() );
The Action can be shared by all menu items.
Or you can look up the default Action from the ActionMap of the text field. See Key Bindings for the action name to use for the lookup. It will also show you the default key binding used for the Action.
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 a beginner using GWT. I have a menubar which I want to retain on the screen even if the mouse is not over it. However when the mouse is not over the menubar and clicked somewhere on the screen then I want the menubar to disappear. I tried using the MouseOutEvent but I need it to fire only when the mouse is clicked not just out. Any help would be appreciated.
this.menu.addDomHandler(menuHoverOutHandler, MouseOutEvent.getType());
MouseOutHandler menuHoverOutHandler = new MouseOutHandler() {
public void onMouseOut(MouseOutEvent event) {
Window.alert("I am outside the region");
}
};
Below is a fully working answer taken from my live app:
The way I solved the menu close on mouse out was to run a boolean variable "isMouseOut" in the top of the constructor to keep track, and then allocate the MouseListener in a more OO friendly way to keep track of the multiple MouseIn-MouseOut events as a user interacts with the menu. Which calls a separate menuClear method acting upon the state of the boolean "isMouseOut". The class implements MouseListener. This is how its done.
Create an ArrayList adding all the menu items to this array first. Like so:
Font menuFont = new Font("Arial", Font.PLAIN, 12);
JMenuBar menuBar = new JMenuBar();
getContentPane().add(menuBar, BorderLayout.NORTH);
// Array of MenuItems
ArrayList<JMenuItem> aMenuItms = new ArrayList<JMenuItem>();
JMenuItem mntmRefresh = new JMenuItem("Refresh");
JMenuItem mntmNew = new JMenuItem("New");
JMenuItem mntmNormal = new JMenuItem("Normal");
JMenuItem mntmMax = new JMenuItem("Max");
JMenuItem mntmStatus = new JMenuItem("Status");
JMenuItem mntmFeedback = new JMenuItem("Send Feedback");
JMenuItem mntmEtsyTWebsite = new JMenuItem("EtsyT website");
JMenuItem mntmAbout = new JMenuItem("About");
aMenuItms.add(mntmRefresh);
aMenuItms.add(mntmNew);
aMenuItms.add(mntmNormal);
aMenuItms.add(mntmMax);
aMenuItms.add(mntmStatus);
aMenuItms.add(mntmFeedback);
aMenuItms.add(mntmEtsyTWebsite);
aMenuItms.add(mntmAbout);
then iterate over the arrayList at this stage adding a MouseListener using the for() loop:
for (Component c : aMenuItms) {
if (c instanceof JMenuItem) {
c.addMouseListener(ml);
}
}
Now set JMenu parents for the MenuBar:
// Now set JMenu parents on MenuBar
final JMenu mnFile = new JMenu("File");
menuBar.add(mnFile).setFont(menuFont);
final JMenu mnView = new JMenu("View");
menuBar.add(mnView).setFont(menuFont);
final JMenu mnHelp = new JMenu("Help");
menuBar.add(mnHelp).setFont(menuFont);
Then add the dropdown menuItems children to the JMenu parents:
// Now set menuItems as children of JMenu parents
mnFile.add(mntmRefresh).setFont(menuFont);
mnFile.add(mntmNew).setFont(menuFont);
mnView.add(mntmNormal).setFont(menuFont);
mnView.add(mntmMax).setFont(menuFont);
mnHelp.add(mntmStatus).setFont(menuFont);
mnHelp.add(mntmFeedback).setFont(menuFont);
mnHelp.add(mntmEtsyTWebsite).setFont(menuFont);
mnHelp.add(mntmAbout).setFont(menuFont);
Add the mouseListeners to the JMenu parents as a separate step:
for (Component c : menuBar.getComponents()) {
if (c instanceof JMenu) {
c.addMouseListener(ml);
}
}
Now that the child menuItem elements all have their own listeners that are separate to the parent JMenu elements and the MenuBar itself - It is important to identify the object type within the MouseListener() instantiation so that you get the menu auto opening on mouseover (in this example the 3x JMenu parents) BUT ALSO avoids child exception errors and allows clean identification of mouseOUT of the menu structure without trying to monitor where the mouse position is. The MouseListener is as follows:
MouseListener ml = new MouseListener() {
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
isMouseOut = true;
timerMenuClear();
}
public void mouseEntered(MouseEvent e) {
isMouseOut = false;
Object eSource = e.getSource();
if(eSource == mnHelp || eSource == mnView || eSource == mnFile){
((JMenu) eSource).doClick();
}
}
};
The above only simulates the mouse click into the JMenu 'parents' (3x in this example) as they are the triggers for the child menu dropdowns. The timerMenuClear() method calls on the MenuSelectionManager to empty whatever selectedpath point was live at the time of real mouseOUT:
public void timerMenuClear(){
ActionListener task = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(isMouseOut == true){
System.out.println("Timer");
MenuSelectionManager.defaultManager().clearSelectedPath();
}
}
};
//Delay timer half a second to ensure real mouseOUT
Timer timer = new Timer(1000, task);
timer.setInitialDelay(500);
timer.setRepeats(false);
timer.start();
}
It took me a little testing, monitoring what values I could access within the JVM during its development - but it Works a treat! even with nested menus :) I hope many find this full example very useful.
Use widget's blur handler. It detects when the widget lost focus.
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.
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.