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);
}
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 have a class MyPopupMenu that extends JPopupMenu. Inside this popup menu I add a JMenuItem with an ActionListener which calls a process that it takes some minutes to return. I would like to make the popup menu close right after this item is pressed. My method inside the MyPopupMenu class is this:
private JMenuItem newItem(){
JMenuItem item=new JMenuItem();
item.setText("One");
item.setToolTipText("One");
ActionListener mylistener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
MyPopupMenu.this.setVisible(false);
Class1 class1=new Class1(file);
try {
class1.start();
} catch (IOException e) {
e.printStackTrace();
}
}
};
item.addActionListener(mylistener);
return item;
}
This doesn't work. After I press the item "One" the process starts but the popup menu remains open until the process returns (for some minutes). Is it possible to make the the popup menu disappear but the process continue running?
You are performing an action on the Swing main thread. This blocks your Gui from updating. You should move the starting of you class into another Thread.
For more info see Here
can i call actionperformed method with jmenu using swing
i am using the following code
JMenu menu1= new JMenu("File");
MenuBar mb= new MenuBar();
mb.add(menu1);
set JmenuBar(mb)
menu1.addActionListener(this);
public void actionPerformed(ActionEvent ae)
{
JOptionPane.showMessaageDialog(null,"menu clicked");
// but its not working
}
thanks in Advance
The action a JMenu is designed to perform is to open a popup with JMenuItems, it doesn't support doing anything else (and even if it did, it would confuse your users). Custom actions are supposed to be handled by JMenuItems in the popup. Install them with something like:
JMenu menu ..
Action myAction = new AbstractAction("Do XY") {
public void actionPerformed(..) {
// implement doing XY
}
};
menu.add(myAction);
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 am pretty sure this is very easy and that I am only missing one line or two but I just cannot make this work despite searching for solutions over the internet. I am fairly new to java and my problem is in a desktop application.
I have a pretty simple desktop application with one text area, one menu bar with one menu and 3 menu item. I want to edit the text of the text area when I click on the Statistic menu item in a JFrame.
Here is the part of the code where I create the menu bar, menu and menu items (as well as their events):
//menu
mnuRevision.setText("Revision");
mnuitmStats.setText("Statistique");
mnuitmStats.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
mnumnuitmStatsMouseClicked(evt);
}
});
mnuRevision.add(mnuitmStats);
mnuitmOrthographe.setText("Grammaire et orthographe");
mnuitmOrthographe.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
mnuitmOrthographeMouseClicked(evt);
}
});
mnuRevision.add(mnuitmOrthographe);
mnuitmAnalyse.setActionCommand("Analyse");
mnuitmAnalyse.setText("Analyse");
mnuRevision.add(mnuitmAnalyse);
jMenuBar1.add(mnuRevision);
setJMenuBar(jMenuBar1);
Here is the Mousclicked function:
private void mnumnuitmStatsMouseClicked(java.awt.event.MouseEvent evt){
this.txtTexte.setText("asdf");
this.repaint();
}
What I want to do is when I click on mnuitemStats, the txtTexte will get the text "asdf" written in it. Somehow, this is not working. It looks like the program is not even getting into the function. I looked on some tutorials and they pretty much have the same code as me except for the objects names since most tutorials uses JButton instead of JMenuItem.
I can provide my whole code if it is needed, but I thought the rest would be irrelevant since it does not touch the menu bar or the textarea. I am using Eclipse Java EE IDE.
I usually write something like
mnuitemStats.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent event)
{// your logic here;
}});
Assuming mnuitmStats is a JMenuItem, it should be. A little more code would be helpful but given that assumption you should use ActionListener not a MouseListener for this.
Something like:
class MenuActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//do something
}
}
and
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
JMenuItem newMenuItem = new JMenuItem("New");
newMenuItem.addActionListener(new MenuActionListener());
fileMenu.add(newMenuItem);