首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >扩展JMenuItem (SplitMenuItem)

扩展JMenuItem (SplitMenuItem)
EN

Stack Overflow用户
提问于 2021-09-19 21:57:57
回答 1查看 115关注 0票数 1

单击SplitMenuItem的行为与任何其他常规JMenuItem一样。但是SplitMenuItem的右侧有一个额外的向下箭头,当单击该箭头时,将出现一个JComboBox类似下拉菜单,您可以从中选择进一步的操作。此功能通常是在应用子菜单时实现的。SplitMenuItem的思想来源于这样一种情况,即在频率上有一个主要的动作,而其他一些与该主要动作相关的动作却很少被选择。对于SplitMenuItem,这个主要操作总是可以直接访问的,因为子菜单的打开已经变得不必要了。我使用了@MadProgrammer的SplitButton中的代码,并将其修改为JMenuItem,但仍然不能接受的是:

  • flicker
  • unusual行为SplitMenuItem的弹出displayed.

  1. 上没有在鼠标上突出显示项目取消或关闭项是强制性的,因为弹出窗口在“外部”单击时没有关闭。
  2. 父菜单完全正常,而弹出窗口是displayed.

到目前为止我尝试过的是:

将所有listeners

  • Attaching a FocusListener (focusLost)移除给SplitMenuItem的父级(即JPopupMenu)。它不会被触发。

为了让MCV运行,我封装了大部分SplitMenuItem类。但实际上,除了将JButton更改为JMenuItem之外,实际上只修改了一个参数构造函数和方法showPopupMenu()。

代码语言:javascript
复制
/**
 * A JMenuItem that has an additional section with an arrow icon on the right 
 * that when clicked shows a JPopupMenu that is positioned flush with the
 * menu item.
 * 
 * Credit:
 * An adaptation of SplitButton finalized by MadProgrammer and DUDSS.
 * https://stackoverflow.com/questions/36352707/actions-inside-of-another-action-like-netbeans
 * Applying code from Darryl Burke's StayOpenMenuItem.
 * https://tips4java.wordpress.com/2010/09/12/keeping-menus-open/
 *
*/

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;

public class SplitMenuItem extends JMenuItem {
    private int separatorSpacing = 4;
    private int splitWidth = 22;
    private int arrowSize = 8;
    private boolean onSplit;
    private Rectangle splitRectangle;
    private boolean alwaysDropDown;
    private Color arrowColor = Color.BLACK;
    private Color disabledArrowColor = Color.GRAY;
    private Image image;
    private MouseHandler mouseHandler;

    private JPopupMenu jpopupMenu;

//  From Darryl Burke's StayOpenMenuItem.
    private static MenuElement[] path;

    {
      getModel().addChangeListener(new ChangeListener() {

        @Override
        public void stateChanged(ChangeEvent e) {
          if (getModel().isArmed() && isShowing()) {
            path = MenuSelectionManager.defaultManager().getSelectedPath();
          }
        }
      });
    }


    public SplitMenuItem(JMenu parent) {
        super();
        addMouseMotionListener(getMouseHandler());
        addMouseListener(getMouseHandler());
        // Default for no "default" action...
        setAlwaysDropDown(true);
//      The next line prevents the JMenu's item list/JPopupMenu to become
//      invisible when clicking on SplitMenuItem's arrow.
        setUI(new StayOpenMenuItemUI());

        InputMap im = getInputMap(WHEN_FOCUSED);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                                                        "PopupMenu.close");
        ActionMap am = getActionMap();
        am.put("PopupMenu.close", new ClosePopupAction());
        JPopupMenu parentPop= parent.getPopupMenu();
/*      Never fired.
        parentPop.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                DBG.p("ParentMenu lost focus");
            }
        });
*/
        parentPop.addPopupMenuListener(new PopupMenuListener() {
            public void popupMenuCanceled(PopupMenuEvent e) {
            }

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
//              This method is called before any actionPerformed in child-popup.
                Timer t = new javax.swing.Timer(0, new ActionListener() {
                  public void actionPerformed(ActionEvent e) {
                    if (jpopupMenu.isVisible())
                    MenuSelectionManager.defaultManager().setSelectedPath(path);
                  }
                });
                t.setRepeats(false);
                t.start();
            }

            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            }
        });
    }

    public SplitMenuItem(JMenu parent, String text) {
        this(parent);
        setText(text);
    }

    public SplitMenuItem(JMenu parent, String text, JPopupMenu popup) {
        this(parent);
        setText(text);
        setPopupMenu(popup);
    }

    @Override
    public void addActionListener(ActionListener l) {
        if (l != null) {
             setAlwaysDropDown(false);
        }
        super.addActionListener(l);
    }

    protected void closePopupMenu() {
        getPopupMenu().setVisible(false);
    }

    @Override
    protected void fireActionPerformed(ActionEvent event) {
        // This is a little bit of a nasty trick.  Basically this is where
        // we try and decide if the menuItems "default" action should
        // be fired or not.  We don't want it firing if the menuItem
        // is in "options only" mode or the user clicked on the
        // "drop down arrow".
        if (onSplit || isAlwaysDropDown()) {
            showPopupMenu();
        } else {
            super.fireActionPerformed(event);
        }
    }

    /**
     * Gets the image to be drawn in the split part. If no is set, a new image
     * is created with the triangle.
     *
     * @return image
     */
    public Image getImage() {
        if (image == null) {
            Graphics2D g = null;
            BufferedImage img = new BufferedImage(arrowSize, arrowSize,
                                                  BufferedImage.TYPE_INT_RGB);
            g = (Graphics2D) img.createGraphics();
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, img.getWidth(), img.getHeight());
            g.setColor(jpopupMenu != null ? arrowColor : disabledArrowColor);
            //this creates a triangle facing right >
            g.fillPolygon(new int[]{0, 0, arrowSize/2},
                          new int[]{0, arrowSize, arrowSize/2}, 3);
            g.dispose();
            //rotate it to face downwards
            img = rotate(img, 90);
            BufferedImage dimg = new BufferedImage(img.getWidth(),
                                  img.getHeight(), BufferedImage.TYPE_INT_ARGB);
            g = (Graphics2D) dimg.createGraphics();
            g.setComposite(AlphaComposite.Src);
            g.drawImage(img, null, 0, 0);
            g.dispose();
            for (int i = 0; i < dimg.getHeight(); i++) {
                for (int j = 0; j < dimg.getWidth(); j++) {
                    if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
                        dimg.setRGB(j, i, 0x8F1C1C);
                    }
                }
            }

            image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
        }
        return image;
    }

    @Override
    public Insets getInsets() {
        Insets insets = (Insets) super.getInsets().clone();
        insets.right += splitWidth;
        return insets;
    }

    @Override
    public Insets getInsets(Insets insets) {
        Insets insets1 = getInsets();
        insets.left = insets1.left;
        insets.right = insets1.right;
        insets.bottom = insets1.bottom;
        insets.top = insets1.top;
        return insets1;
    }

    protected MouseHandler getMouseHandler() {
        if (mouseHandler == null) {
            mouseHandler = new MouseHandler();
        }
        return mouseHandler;
    }

    protected int getOptionsCount() {
        return getPopupMenu().getComponentCount();
    }

    /**
     * Returns the menuItems popup menu.
     *
     * @return
     */
    public JPopupMenu getPopupMenu() {
        if (jpopupMenu == null) {
            jpopupMenu = new JPopupMenu();
        }
        return jpopupMenu;
    }

    /**
     *Show the dropdown menu, if attached, even if the menuItem part is clicked.
     *
     * @return true if alwaysDropdown, false otherwise.
     */
    public boolean isAlwaysDropDown() {
        return alwaysDropDown;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        //Graphics gClone = g.create();//EDIT: Hervé Guillaume
        Color oldColor = g.getColor();
        splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth,
                                        getHeight());
        g.translate(splitRectangle.x, splitRectangle.y);
        int mh = getHeight() / 2;
        int mw = splitWidth / 2;
        g.drawImage(getImage(), mw-arrowSize/2, mh+2 - arrowSize/2, null);
        if (!alwaysDropDown) {
            if (getModel().isRollover() || isFocusable()) {
                g.setColor(UIManager.getLookAndFeelDefaults()
                        .getColor("MenuItem.background"));
                g.drawLine(1, separatorSpacing + 2, 1,
                        getHeight() - separatorSpacing - 2);
                g.setColor(UIManager.getLookAndFeelDefaults()
                        .getColor("MenuItem.shadow"));
                g.drawLine(2, separatorSpacing + 2, 2,
                        getHeight() - separatorSpacing - 2);
            }
        }
        g.setColor(oldColor);
        g.translate(-splitRectangle.x, -splitRectangle.y);
    }

    /**
     * Rotates the given image with the specified angle.
     *
     * @param img image to rotate
     * @param angle angle of rotation
     * @return rotated image
     */
    private BufferedImage rotate(BufferedImage img, int angle) {
        int w = img.getWidth();
        int h = img.getHeight();
        BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
        Graphics2D g = dimg.createGraphics();
        g.rotate(Math.toRadians(angle), w / 2, h / 2);
        g.drawImage(img, null, 0, 0);
        return dimg;
    }

    /**
     *Show the dropdown menu, if attached, even if the menuItem part is clicked.
     *
     * If true, this will prevent the menuItem from raising any actionPerformed
     * events for itself.
     *
     * @param value true to show the attached dropdown even if the menuItem part
     * is clicked, false otherwise
     */
    public void setAlwaysDropDown(boolean value) {
        if (alwaysDropDown != value) {
            this.alwaysDropDown = value;
            firePropertyChange("alwaysDropDown", !alwaysDropDown,
                                                                alwaysDropDown);
        }
    }

    public void setPopupMenu(JPopupMenu popup) {
        jpopupMenu = popup;
        this.setComponentPopupMenu(popup);
    }


    protected void showPopupMenu() {
        if (getOptionsCount() > 0) {
            JPopupMenu popup = getPopupMenu();
            Point p= getLocationOnScreen();
            popup.setLocation(p.x+getWidth() - popup.getPreferredSize().width,
                              p.y+getHeight());
            popup.setVisible(true);
//            Must be showing on the screen to determine its location.
//            popup.show(this, (getWidth() - popup.getWidth()), getHeight());
        }
    }

/*
    private JMenu getMenu() {
      JMenu menu = null;
      while (menu == null) {
        JPopupMenu popup = (JPopupMenu)this.getParent();
        JMenuItem item = (JMenuItem)popup.getInvoker();
        if (!(item.getParent() instanceof JPopupMenu)) menu = (JMenu)item;
      }
      return menu;
    }
*/

    protected class ClosePopupAction extends AbstractAction {
        @Override
        public void actionPerformed(ActionEvent e) {
            closePopupMenu();
        }
    }

    protected class MouseHandler extends MouseAdapter {
        @Override
        public void mouseExited(MouseEvent e) {
            onSplit = false;
            repaint(splitRectangle);
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            if (splitRectangle.contains(e.getPoint())) {
                onSplit = true;
            } else {
                onSplit = false;
            }
            repaint(splitRectangle);
        }
    }
}


/**************************************************************************/
import javax.swing.*;
import javax.swing.plaf.basic.*;

class StayOpenMenuItemUI extends BasicMenuItemUI {
 
  @Override
  protected void doClick(MenuSelectionManager msm) {
    menuItem.doClick(0);
  }
}


/**************************************************************************/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SplitMenuItemTest extends JFrame {
  public static final long serialVersionUID = 100L;
  JMenuItem exitItem, welcomeItem;
  SplitMenuItem splitItem;

  public SplitMenuItemTest() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(300, 240);
    setLocationRelativeTo(null);

    JMenuBar menuBar= new JMenuBar();
    setJMenuBar(menuBar);
    JMenu menu= new JMenu("A menu");
    menuBar.add(menu);

    welcomeItem= new JMenuItem("Welcome");
    ActListener actListener= new ActListener();
    welcomeItem.addActionListener(actListener);
    menu.add(welcomeItem);

    JPopupMenu popup= createPopupForItem();
    splitItem= new SplitMenuItem(menu, "Most often this", popup);
    splitItem.addActionListener(e -> {
      JOptionPane.showMessageDialog(SplitMenuItemTest.this,
        "The usual action of this menuItem will be performed.");
    });
    menu.add(splitItem);

    exitItem= new JMenuItem("Exit");
    exitItem.addActionListener(actListener);
    menu.add(exitItem);
    setVisible(true);
  }


  static public void main(String args[]) {
    EventQueue.invokeLater(SplitMenuItemTest::new);
  }


  private JPopupMenu createPopupForItem() {
    JPopupMenu popup= new JPopupMenu();
    JMenuItem seldomItem= popup.add("Seldomly used");
    seldomItem.addActionListener(e -> {
      System.out.println(seldomItem.getText());
      popup.setVisible(false);
    });
    JMenuItem rareTaskItem= popup.add("Rare task");
    rareTaskItem.addActionListener(e -> {
      System.out.println(rareTaskItem.getText());
      popup.setVisible(false);
    });
    popup.addSeparator();
    JMenuItem cancelItem= popup.add("Cancel"); // Mandatory JMenuItem.
    cancelItem.addActionListener(e -> {
      popup.setVisible(false);
    });
    return popup;
  }


  public class ActListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      Object obj= e.getSource();
      if (obj==exitItem)
        System.exit(0);
      else if (obj==welcomeItem)
        System.out.println("Welcome");
      else
        System.out.println("SplitItem was clicked.");
    }
  }

}
EN

回答 1

Stack Overflow用户

发布于 2021-09-21 06:27:41

如果使用带有JMenu派生类的子菜单,该类以水平线呈现(也可以用文本"More actions.“代替)如何?或者别的什么)?

那么您的导航问题就应该消失了,而外观和感觉仍然是一样的。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69247363

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档