Как создать раскрывающееся меню на панели инструментов Java Swing?

Я создал раскрывающееся меню на моем Swing JToolBar. Но это не создает вести себя так, как я хочу. Я стремлюсь к тому, чтобы она работала как кнопка «Умные закладки» в Firefox.

Оно исчезает, когда пользователь выбирает пункт меню: ПРАВИЛЬНО!

Он исчезает, когда пользователь нажимает ESC: ПРАВИЛЬНО!

Оно исчезает, когда пользователь щелкает где-нибудь в главном фрейме за пределами меню: ПРАВИЛЬНО!

Но он не исчезает, когда пользователь второй раз нажимает кнопку, показывающую раскрывающееся меню: НЕПРАВИЛЬНО... :-(

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

Вот мой текущий код из Java 6 на Mac:

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());
    }
}

person Steve McLeod    schedule 14.12.2009    source источник


Ответы (4)


Что ж, вот потенциальное решение, которое не лишено недостатков. Только вы можете решить, приемлемо ли это для вашего приложения. Проблема заключается в том, что закрытие всплывающего окна происходит до того, как будут запущены другие события обработки мыши, поэтому повторное нажатие кнопки «Дополнительно..» приводит к тому, что всплывающее окно скрывается, таким образом сбрасывая состояние кнопок до невыбранного ДО того, как кнопка даже получит сообщение о том, что она была нажата.

Простой обходной путь — добавить следующий вызов в вашу основную программу:

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

Результатом этого является то, что всякий раз, когда всплывающее меню закрывается из-за события нажатия мыши, это событие мыши будет использовано во время закрытия меню и не будет передано никаким другим компонентам под мышью. Если вы можете жить с ограничениями, это простое решение.

person BryanD    schedule 17.12.2009
comment
Работает как шарм... Вопрос: как вы узнали об этом решении? Это где-то задокументировано? - person Steve McLeod; 18.12.2009
comment
На самом деле я просто использовал свой отладчик (с прикрепленным JDK src), чтобы узнать, где и когда запускаются события. Я нашел его в BasicPopupMenuUI. - person BryanD; 18.12.2009

Что происходит, так это то, что когда вы щелкаете по меню, всплывающее меню отменяется, поэтому вы отменяете выбор кнопки, но следующим немедленным событием является нажатие кнопки, и теперь ее выбор отменяется, поэтому снова отображается меню.

У меня пока нет точного решения, но дайте мне немного...

person Reverend Gonzo    schedule 17.12.2009

Я не использую Firefox, поэтому я не знаю, как выглядит кнопка Smart Bookmarks, но, возможно, использую JMenu в качестве «кнопки». Вы можете попробовать использовать границу JButton, чтобы она больше походила на кнопку.

person camickr    schedule 14.12.2009
comment
Таким образом, вы голосуете против предложения, потому что оно не похоже на то, что вы хотите. Полезно знать, в следующий раз, когда я буду работать, дам вам нестандартное предложение. У меня все работает, поэтому я не знаю, какие у вас проблемы. - person camickr; 17.12.2009
comment
Camickr, я не хотел обидеть. Я проголосовал против, потому что ваш ответ не работает, поскольку он вообще не дает поведения смарт-закладок Firefox. Ради будущих людей с тем же вопросом, я голосую за неправильные ответы и за правильные ответы. - person Steve McLeod; 17.12.2009
comment
Как я уже сказал, я не знаю, как работают Smart Bookmarks (или как они выглядят), поэтому я предложил вам мыслить нестандартно. Как я уже сказал, он отлично работает для меня. Вы щелкаете по нему, и появляется раскрывающийся список, и вы нажимаете на пункт меню, поэтому я не знаю, что вам не нравится в этом предложении. - person camickr; 17.12.2009

Слушатель на кнопке реагирует только тогда, когда она нажата, потому что вы слушаете только 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 где-нибудь, или вы можете сделать так, чтобы меню само добавило к кнопке еще один прослушиватель. Последнее решение может быть более простым, поскольку вы уже отправляете ссылку на кнопку в меню.

person Joonas Pulakka    schedule 14.12.2009
comment
Йоонас, ты пробовал свое предложение? Я пробовал это, и это, кажется, не работает. Всплывающее меню запускает событие отмены при нажатии кнопки, которое отменяет выбор кнопки. Это гарантирует, что кнопка всегда будет выбрана при нажатии на нее. - person Steve McLeod; 14.12.2009
comment
Я не проверял приведенный выше код, но я уверен, что он должен работать так, как я описываю. Может быть, я чего-то не понимаю, но в зависимости от события popupMenuCanceled() кажется хрупким, поскольку в документации только говорится, что этот метод вызывается при отмене всплывающего меню. Что это должно означать? Я предлагаю просто добавить явный прослушиватель состояния кнопки. - person Joonas Pulakka; 14.12.2009
comment
Йоонас, проблема в том, что твое предложение просто не работает. Попробуйте и посмотрите. - person Steve McLeod; 14.12.2009
comment
облом. Кажется, что кнопка генерирует события ItemEvent.SELECTED в странных случаях, например, при вызове setSelected(false). Итак, это довольно хороший вопрос, извините, что сейчас нет времени углубиться в него. - person Joonas Pulakka; 14.12.2009
comment
Ну, реальная проблема заключается в том, что все события в PopupListener вызываются до того, как JToggleButton даже получает уведомление о том, что он был нажат. Следовательно, при нажатии на кнопку-переключатель, чтобы закрыть всплывающее окно, всплывающее окно отменяет его выбор, а ТОГДА сама логика кнопки обрабатывает щелчок, видит, что она больше не выбрана, и меняет состояние на выбранное... таким образом помещая всплывающее окно назад снова. - person BryanD; 17.12.2009