Below is a complete compiling running failing example illustrating my question. JMenuItem extends JComponent. When added to JPopupMenu and shown in a context menu... it is... well... SHOWN, but the componentShown
method is not called. How can I know when the JMenuItem is shown? I need to know from within the JMenuItem itself. I'm creating this JMenuItem and handing it off to a larger framework. I have no knowledge or control of the JPopupMenu, container, or any other components. When my JMenuItem is shown, I must update its text based on the context and current state of the app. How do I know when it is shown?
In the following example, the text "component shown" is never printed under any circumstances. Right click inside the JPanel to get the context menu and you'll see the JMenuItem text, "Reply Hi", but now output to the console.
I'm running on macOS 11.5.2 Big Sur and JDK 11.0.6 LTS (from Oracle).
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class JMenuItemListeners {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.setMinimumSize(new Dimension(800, 600));
panel.setLayout(new BorderLayout());
panel.add(new JLabel("Hello World... right click me."),
BorderLayout.CENTER);
//========================================
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem menuItem = new JMenuItem("Reply Hi");
menuItem.addComponentListener(
new ComponentListener() {
@Override
public void componentResized(ComponentEvent e) {}
@Override
public void componentMoved(ComponentEvent e) {}
@Override
public void componentShown(ComponentEvent e) {
System.out.println("component shown");
}
@Override
public void componentHidden(ComponentEvent e) {}
}
);
popupMenu.add(menuItem);
panel.setComponentPopupMenu(popupMenu);
//========================================
frame.setContentPane(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
CodePudding user response:
You can use an AncestorListener
:
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class JMenuItemListeners {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.setMinimumSize(new Dimension(800, 600));
panel.setLayout(new BorderLayout());
panel.add(new JLabel("Hello World... right click me."),
BorderLayout.CENTER);
//========================================
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem menuItem = new JMenuItem("Reply Hi");
menuItem.addAncestorListener(new AncestorListener()
{
@Override
public void ancestorRemoved(AncestorEvent e) {}
@Override
public void ancestorMoved(AncestorEvent e) {}
@Override
public void ancestorAdded(AncestorEvent e) {
System.out.println("ancestor shown");
}
});
popupMenu.add(menuItem);
panel.setComponentPopupMenu(popupMenu);
//========================================
frame.setContentPane(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
CodePudding user response:
I don't think that you'll find some kind of listener that is notified when a JMenuItem
is shown.
I would instead try to decouple the JMenuItem
from the state of your application and I would do that using a javax.swing.AbstractAction
.
You can update the AbstractAction
anytime the context and / or state of your application changes and magically when the JMenuItem
is shown it will reflect the context and state of your application.
The code fragment below contains replaces the creation of the JPopupMenu
and the JMenuItem
. The second part shows how you would update the AbstractAction
by turning the JMenuItem
into a clock.
//========================================
JPopupMenu popupMenu = new JPopupMenu();
AbstractAction a = new AbstractAction("Reply Hi") {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hi");
}
};
JMenuItem menuItem = new JMenuItem(a);
popupMenu.add(menuItem);
panel.setComponentPopupMenu(popupMenu);
//========================================
Timer t = new Timer(100, (e) -> {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("'Its 'HH:mm:ss");
a.putValue(Action.NAME, dtf.format(LocalTime.now()));
});
t.start();
//========================================