2016-11-14 2 views
0

Я знаю, как создавать подменю Java Swing с помощью JMenu. При наведении курсора мыши на объект JMenu, он отображает JPopupMenu показывая пункты подменю, как это:Динамически создавать подменю Java Swing

субменю JMenu

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

Моя проблема заключается в том, что в моем приложении, определяя, какие элементы меню будет иметь подменю дорого. Я не хочу заранее определять, должен ли конкретный элемент меню быть 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, остальная часть меню закрывается. В результате дисплей не выглядит, как раньше, а так:

Подменю отображается вручную

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

Обратите внимание, что я не сделал нажмите на пункт меню «B», только переехал мышь в нее. Меню не закрывалось из-за щелчка мышью.

Как я могу сделать то, что делает JMenu - отображает JPopupMenu, не закрывая остальное меню?

+0

Может быть, вы могли бы попробовать заменить JMenuItem с JMenu после того, как вы определяете это на самом деле действительно есть подменю. – camickr

+0

Я думаю, что новая идиома, подобная этой, приведет к запутыванию пользователей, и вам будет лучше, если ваша запись в меню всегда будет JMenuItem, которая вызывает модальный диалог (возможно, JOptionPane), показывающий динамически определенные варианты. – VGR

+0

Спасибо, camickr и VGR, за ваши предложения по обходным решениям. Даже если мне нужно использовать обходной путь, я бы точно хотел узнать, почему показ всплывающего окна заставляет меню закрываться. Очевидно, что это можно сделать, не закрывая меню, поскольку JMenu это делает. – Vine

ответ

0

подход я предварительно решил на это продлить JMenu вместо из JMenuItem и использовать этот тип для всех моих элементов меню. Но I не будет заполнить эти элементы (дорогой шаг), пока пользователь не попросит его, наведя указатель мыши.

Чтобы избежать загромождения меню со стрелками значками, которые JMenu обычно отображает (потенциально вводящие в заблуждение в данном случае), я использую a technique described by Stackoverflow's MadProgrammer для создания экземпляра arrowless JMenu в статическом методе фабрики. Поскольку I восстанавливает свойство значка стрелки после создания стрелки JMenu, обычных экземпляров JMenu, созданных в другом месте, по-прежнему будет отображаться стрелка.

Некоторые элементы меню должны будут выполнять действия и закрывать меню, , как JMenuItem. JMenu обычно не отвечает на мыши кликов, поэтому я выполняю действия click в моем 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

Смежные вопросы