【问题标题】:Dynamically create Java Swing submenus动态创建 Java Swing 子菜单
【发布时间】:2016-11-20 23:10:16
【问题描述】:

我知道如何使用 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,一旦你确定它确实有子菜单。
  • 我认为像这样的新成语会让用户感到困惑,你最好让你的菜单条目始终是一个 JMenuItem,它会打开一个模式对话框(可能是一个 JOptionPane)显示动态确定的选择。
  • 感谢 camickr 和 VGR 对解决方法的建议。即使我必须使用解决方法,我也很想知道为什么显示弹出窗口会导致菜单关闭。显然它可以在不关闭菜单的情况下完成,因为 JMenu 会这样做。
  • 研究 JDK 代码,我至少弄清楚是怎么回事。如果在 JPopupMenu.show() 中指定的“调用者”不是 JMenu 的实例,则 JPopupMenu 确定它是“独立”弹出窗口并调用 MenuSelectionManager.setSelectedPath() 以使其自身成为唯一的路径元素。 MenuSelectionManager 然后关闭所有以前的路径元素。现在要找到一个好的解决方案......

标签: java swing submenu


【解决方案1】:

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

为了避免使用 JMenu 的箭头图标弄乱菜单 通常显示(在这种情况下可能会产生误导),我使用a technique described by Stackoverflow'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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    • 2013-03-29
    • 2021-07-15
    • 1970-01-01
    • 2015-04-08
    • 1970-01-01
    • 2023-03-29
    相关资源
    最近更新 更多