如何在Java Swing工具栏中创建“下拉”菜单?

9
我在Swing JToolBar上创建了一个下拉菜单,但它并没有按照我想要的方式工作。我希望它能像Firefox的“智能书签”按钮一样工作。
当用户选择菜单项时,它会消失:正确!
当用户按ESC键时,它会消失:正确!
当用户在菜单外的主窗口中某个地方单击时,它会消失:正确!
但是,当用户第二次单击显示下拉菜单的按钮时,它不会消失:不正确... :-(
我的问题是如何添加此行为,使其在第二次单击显示菜单的按钮时消失。
以下是我目前的代码,来自Mac上的Java 6:
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class ScratchSpace {

    public static void main(String[] arguments) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("Toolbar with Popup Menu demo");

                final JToolBar toolBar = new JToolBar();
                toolBar.add(createMoreButton());

                final JPanel panel = new JPanel(new BorderLayout());
                panel.add(toolBar, BorderLayout.NORTH);
                panel.setPreferredSize(new Dimension(600, 400));
                frame.getContentPane().add(panel);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    private static AbstractButton createMoreButton() {
        final JToggleButton moreButton = new JToggleButton("More...");
        moreButton.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    createAndShowMenu((JComponent) e.getSource(), moreButton);
                }
            }
        });
        moreButton.setFocusable(false);
        moreButton.setHorizontalTextPosition(SwingConstants.LEADING);
        return moreButton;
    }

    private static void createAndShowMenu(final JComponent component, final AbstractButton moreButton) {
        JPopupMenu menu = new JPopupMenu();
        menu.add(new JMenuItem("Black"));
        menu.add(new JMenuItem("Red"));

        menu.addPopupMenuListener(new PopupMenuListener() {
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            }

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                moreButton.setSelected(false);
            }

            public void popupMenuCanceled(PopupMenuEvent e) {
                moreButton.setSelected(false);
            }
        });

        menu.show(component, 0, component.getHeight());
    }
}
4个回答

5

嗯,这里有一个潜在的解决方案,但也存在一些缺陷。你可以根据自己的应用需求来决定是否接受这个方案。问题在于弹出窗口的关闭发生在其他鼠标事件之前,因此再次点击“更多”按钮会导致弹出窗口隐藏,从而在按钮甚至不知道自己被按下之前将其状态重置为取消选中状态。

一个简单的解决方法是在主程序中添加以下调用:

UIManager.put("PopupMenu.consumeEventOnClose", Boolean.TRUE);

这会导致每当弹出菜单因为鼠标按下事件而关闭时,该鼠标事件将被消耗,不会传递给鼠标下的任何其他组件。如果你可以接受这种限制,这是一个简单的解决方案。

运行得很好... 一个问题:你是怎么知道这个解决方案的?有文档记录吗? - Steve McLeod
1
我实际上只是使用我的调试器(附带JDK源代码)来查找事件何时、何地被触发。我在BasicPopupMenuUI中找到了它。 - BryanD

1

目前的情况是,当您点击菜单时,它会取消弹出菜单,因此您取消选择该按钮,但下一个即时事件是单击该按钮,现在取消了选择,因此它再次显示菜单。

我还没有确切的解决方案,但请给我一点时间...


0

我不使用Firefox,所以不知道Smart Bookmarks按钮的样子,但也许可以使用JMenu作为“按钮”。您可以尝试使用JButton的边框使其看起来更像一个按钮。


所以你因为建议不符合你的期望而对其进行了负评。好的,知道了,下次我会给你提供一个“出人意料”的建议。我已经让它工作了,所以我不知道你遇到了什么问题。 - camickr
Camickr,我并不是有意冒犯。我给你的回答投了反对票,因为它并不起作用,根本没有提供Firefox“智能书签”的行为。为了那些未来有同样问题的人,我会将错误的答案投反对票,正确的答案投赞成票。 - Steve McLeod
就像我所说的,我不知道智能书签是如何工作的(或者它长什么样子),所以我提出了一个建议,让你跳出思维定势。正如我所说,它对我来说完全正常。你点击它,下拉菜单出现,然后你再点击菜单项,所以我不知道你对这个建议有什么不喜欢的地方。 - camickr

-1

好的,按钮上的监听器只有在被按下时才会做出反应,因为您只监听ItemEvent.SELECTED事件。那么在这里添加另一个if语句来监听ItemEvent.DESELECTED事件如何:

    moreButton.addItemListener(new ItemListener() {
        public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() == ItemEvent.SELECTED) {
                createAndShowMenu((JComponent) e.getSource(), moreButton);
            }
        }
    });

你可以将menu的引用存储在某个地方,或者你可以让菜单本身向按钮添加另一个监听器。后一种解决方案可能更为直接,因为你似乎已经将按钮引用发送到了菜单中。

Joonas,你试过你的建议了吗?我已经尝试过了,似乎不起作用。弹出菜单在单击按钮时会触发取消事件,这会取消选择该按钮。这确保了当您单击它时,按钮始终设置为选定状态。 - Steve McLeod
我没有精确测试上面的代码,但我相信它应该按照我描述的方式工作。也许我理解有误,但是仅依赖于popupMenuCanceled()事件似乎很脆弱,因为文档只说“当取消弹出菜单时调用此方法”。这是什么意思?我建议为按钮状态添加一个显式监听器。 - Joonas Pulakka
Joonas,问题在于你的建议根本行不通。试一下就知道了。 - Steve McLeod
糟糕。似乎按钮在奇怪的情况下会发出ItemEvent.SELECTED事件,例如在调用setSelected(false)时。所以,这是一个非常好的问题,很抱歉我现在没有时间深入挖掘它。 - Joonas Pulakka
实际问题在于,在 JToggleButton 被通知已被点击之前,PopupListener 中的所有事件都会被调用。因此,当单击切换按钮以关闭弹出窗口时,它会被 PopupListener 取消选择,然后按钮逻辑本身处理点击,发现它不再被选中并将状态更改为选中...从而再次放置弹出窗口。 - BryanD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接