Home > Enterprise >  How do I know when a JMenuItem is shown? Adding a ComponentListener and using componentShown doesn&#
How do I know when a JMenuItem is shown? Adding a ComponentListener and using componentShown doesn&#

Time:09-17

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();
    //========================================
  • Related