动态创建 Java 个 Swing 子菜单

Dynamically create Java Swing submenus

我知道如何使用 JMenu 创建 Java Swing 子菜单。当我们将鼠标悬停在 JMenu 对象上时,它会显示一个显示子菜单项的 JPopupMenu,如下所示:

使用 JMenu 的子菜单

我的问题是,在我的应用程序中,确定哪些菜单元素将具有子菜单的成本很高。我不想提前确定特定菜单元素应该是 JMenu 还是 JMenuItem。我想让每个元素都成为 JMenuItem 并仅在用户请求时才显示它的子菜单,例如,将鼠标悬停在菜单项上。像这样:

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);
  }
}

唯一的问题是,当显示我手动创建的 JPopupMenu 时,菜单的其余部分将关闭。结果显示看起来不像之前的显示,而是像这样:

手动显示子菜单

请注意,我没有在 "B" 菜单项上 单击 ,只是将鼠标移入其中。由于单击鼠标,菜单没有关闭。

我怎样才能像 JMenu 那样做 -- 显示 JPopupMenu 而不关闭菜单的其余部分?

我暂时决定的方法是扩展 JMenu JMenuItem 并将此类型用于 all 我的菜单元素。但是我 不会 填充 这些元素(昂贵的步骤),直到用户 通过悬停鼠标请求它。

为了避免使用 JMenu 的箭头图标使菜单混乱 通常显示(在这种情况下可能会产生误导),我使用 a technique described by Whosebug's MadProgrammer 在静态工厂方法中实例化无箭头的 JMenu。自从我 创建无箭头 JMenu 后恢复箭头图标 属性, 在别处创建的普通 JMenu 实例仍会显示箭头。

一些菜单元素需要执行操作并关闭菜单, 就像 JMenuItem 一样。 JMenu 通常不响应鼠标 点击,所以我在我的 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);
  }
}

结果如我所愿:

Menu3 with open menu