Extended JMenuItem (SplitMenuItem)



单击SplitMenuItem的行为与任何其他常规JMenuItem相同。但是一个SplitMenuItem在其右侧有一个额外的向下箭头,当单击时在这个箭头上,将出现一个类似JComboBox的下拉菜单,您可以选择进一步的操作。此功能通常通过应用子菜单。SplitMenuItem的想法源于这样一种情况一个动作在频率上占主导地位,其他一些动作与这个主要动作有关行动很少被选择。SplitMenuItem的主要动作是始终可直接访问,因为打开子菜单已成为不必要的。我使用了@MadProgrammer的SplitButton代码,并将其改编为JMenuItem,但仍然不可接受的是:

  • >
  • SplitMenuItem弹出的异常行为
    1. 鼠标悬停在
    2. 上的项目不高亮显示
    3. 取消或关闭项是强制性的,因为弹出窗口不会在"外部"上关闭。点击。
    4. 显示弹出窗口时,父菜单功能正常。
  • 我试过了:

    • 删除所有监听器
    • 附加一个FocusListener (focusLost)到SplitMenuItem的父类(这是一个JPopupMenu)。

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

    /**
    * 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.");
    }
    }
    }
    

    如何使用带有jmenu派生类的子菜单,该类以水平线呈现(也可以用文本"More actions…"代替)之类的)?

    那么你的导航问题应该消失了,而外观和感觉仍然是相当相同的。

    相关内容

    • 没有找到相关文章

    最新更新