简体   繁体   English

动态创建Java Swing子菜单

[英]Dynamically create Java Swing submenus

I know how to create Java Swing submenus using JMenu. 我知道如何使用JMenu创建Java Swing子菜单。 When we hover the mouse over a JMenu object, it displays a JPopupMenu showing the submenu items, like this: 当我们将鼠标悬停在JMenu对象上时,它将显示一个JPopupMenu,其中显示了子菜单项,如下所示:

Submenu using JMenu 使用JMenu的子菜单

https://i.stack.imgur.com/K6yz8.png

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. 我不想事先确定某个菜单元素应该是JMenu还是只是JMenuItem。 I want to make every element a JMenuItem and display a submenu for it only if the user requests it by, eg, hovering the mouse over a menu item. 我想将每个元素都设置为JMenuItem,并仅在用户通过(例如,将鼠标悬停在菜单项上)用户请求时显示它的子菜单。 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. 唯一的问题是,当显示我手动创建的JPopupMenu时,菜单的其余部分将关闭。 The resulting display does not look like the earlier one, but rather like this: 结果显示看起来不像以前的显示,而是这样的:

Submenu displayed manually 子菜单手动显示

https://i.stack.imgur.com/cqV7q.png

Note that I did not click on the "B" menu item, only moved the mouse into it. 请注意,我没有单击 “ B”菜单项,只是将鼠标移到了它。 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? 如何执行JMenu的功能-在不关闭菜单其余部分的情况下显示JPopupMenu?

The approach I've tentatively decided upon is to extend JMenu instead of JMenuItem and use this type for all of my menu elements. 我暂时确定的方法是扩展JMenu而不是JMenuItem,并将此类型用于所有菜单元素。 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. 为了避免用JMenu通常显示的箭头图标弄乱菜单(在这种情况下可能会引起误解),我使用了Stackoverflow的MadProgrammer描述的技术 ,以静态工厂方法实例化了无箭头的JMenu。 Since I restore the arrow icon property after creating the arrowless JMenu, normal JMenu instances created elsewhere will still show the arrow. 由于我在创建无箭头的JMenu之后恢复了箭头图标属性,因此在其他位置创建的普通JMenu实例仍会显示箭头。

Some menu elements will need to execute actions and close the menu, like a JMenuItem does. 某些菜单元素将需要执行操作并关闭菜单,就像JMenuItem一样。 A JMenu doesn't normally respond to mouse clicks, so I execute click actions in my MouseListener. 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);
  }
}

The result works the way I want: 结果按我想要的方式工作:

Menu3 with open menu 带有打开菜单的Menu3

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM