Отображение / скрытие JPopupMenu от JButton;FocusListener не работает? - PullRequest
7 голосов
/ 11 марта 2010

Мне нужен был JButton с прикрепленным выпадающим меню стиля. Поэтому я взял JPopupMenu и прикрепил его к JButton так, как вы можете видеть в коде ниже. Что нужно сделать, это:

  • показать всплывающее окно при нажатии
  • скрыть, если щелкнуть второй раз
  • скрыть, если во всплывающем окне выбран элемент
  • скрыть, если пользователь щелкает где-то еще на экране

Эти 4 вещи работают, но из-за используемого мной логического флага, если пользователь щелкает где-то еще или выбирает элемент, мне нужно дважды щелкнуть по кнопке, чтобы она снова появилась. Вот почему я попытался добавить FocusListener (который абсолютно не отвечает), чтобы исправить это и установить флаг false в этих случаях.

РЕДАКТИРОВАТЬ: Последняя попытка в ответном сообщении ...

Вот слушатели: (Это класс, расширяющий JButton, поэтому второй слушатель находится на JButton.)

// Show popup on left click.
menu.addFocusListener(new FocusListener() {
 @Override
 public void focusLost(FocusEvent e) {
  System.out.println("LOST FOCUS");
  isShowingPopup = false;
 }

 @Override
 public void focusGained(FocusEvent e) {
  System.out.println("GAINED FOCUS");
 }
});

addActionListener(new ActionListener() {
 @Override
 public void actionPerformed(ActionEvent e) {
  System.out.println("isShowingPopup: " + isShowingPopup);
  if (isShowingPopup) {
   isShowingPopup = false;
  } else {
   Component c = (Component) e.getSource();
   menu.show(c, -1, c.getHeight());
   isShowingPopup = true;
  }
 }
});

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

Спасибо!

Код:

public class Button extends JButton {

    // Icon.
    private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");

    // Unit popup menu.
    private final JPopupMenu menu;

    // Is the popup showing or not?
    private boolean isShowingPopup = false;

    public Button(int height) {
        super(ARROW_SOUTH);
        menu = new JPopupMenu(); // menu is populated somewhere else

        // FocusListener on the JPopupMenu
        menu.addFocusListener(new FocusListener() {
            @Override
            public void focusLost(FocusEvent e) {
                System.out.println("LOST FOCUS");
                isShowingPopup = false;
            }

            @Override
            public void focusGained(FocusEvent e) {
                System.out.println("GAINED FOCUS");
            }
        });

        // ComponentListener on the JPopupMenu
        menu.addComponentListener(new ComponentListener() {
            @Override
            public void componentShown(ComponentEvent e) {
                System.out.println("SHOWN");
            }

            @Override
            public void componentResized(ComponentEvent e) {
                System.out.println("RESIZED");
            }

            @Override
            public void componentMoved(ComponentEvent e) {
                System.out.println("MOVED");
            }

            @Override
            public void componentHidden(ComponentEvent e) {
                System.out.println("HIDDEN");
            }
        });

        // ActionListener on the JButton
        addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("isShowingPopup: " + isShowingPopup);
                if (isShowingPopup) {
                    menu.requestFocus();
                    isShowingPopup = false;
                } else {
                    Component c = (Component) e.getSource();
                    menu.show(c, -1, c.getHeight());
                    isShowingPopup = true;
                }
            }
        });

        // Skip when navigating with TAB.
        setFocusable(true); // Was false first and should be false in the end.

        menu.setFocusable(true);
    }

}

Ответы [ 7 ]

3 голосов
/ 11 марта 2010

Вот вариант предложенного Амбер Шахом "большого взлома", который я только что сделал. Без флага isShowingPopup ...

Это не пуленепробиваемый, но он работает довольно хорошо, пока кто-то не придет с невероятно медленным щелчком, чтобы закрыть всплывающее окно (или очень быстрый второй щелчок, чтобы открыть его ...).

public class Button extends JButton {

 // Icon.
 private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");

 // Popup menu.
 private final JPopupMenu menu;

 // Last time the popup closed.
 private long timeLastShown = 0;

 public Button(int height) {
  super(ARROW_SOUTH);
  menu = new JPopupMenu(); // Populated somewhere else.

  // Show and hide popup on left click.
  menu.addPopupMenuListener(new PopupMenuListener() {
   @Override
   public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
    timeLastShown = System.currentTimeMillis();
   }
   @Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {}
   @Override public void popupMenuCanceled(PopupMenuEvent arg0) {}
  });
  addActionListener(new ActionListener() {
   @Override
   public void actionPerformed(ActionEvent e) {
    if ((System.currentTimeMillis() - timeLastShown) > 300) {
     Component c = (Component) e.getSource();
     menu.show(c, -1, c.getHeight());
    }
   }
  });

  // Skip when navigating with TAB.
  setFocusable(false);
 }

}

Как я уже говорил в комментариях, это не самое элегантное решение, но оно ужасно простое и работает в 98% случаев.

Открыто для предложений!

1 голос
/ 12 марта 2010

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

Значение FocusListener должно быть следующим:

    menu.addFocusListener(new FocusListener() {
        @Override
        public void focusLost(FocusEvent e) {
            System.out.println("LOST FOCUS");
            isShowingPopup = false;
        }

        @Override
        public void focusGained(FocusEvent e) {
            System.out.println("GAINED FOCUS");
            isShowingPopup = true;
        }
    });

Логическое значение isShowingPopup не изменяется где-либо еще - если оно получает фокус, оно предполагает, что оно показано, и если оно теряет фокус, оно предполагает, что это не так.

Далее ActionListener на кнопке отличается:

   addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("isShowingPopup: " + isShowingPopup);
            if (showPopup) {
                Component c = (Component) e.getSource();
                menu.show(c, -1, c.getHeight());
                menu.requestFocus();
            } else {
                showPopup = true;
            }
        }
    });

Теперь наступает действительно новый бит. Это MouseListener на кнопке:

    addMouseListener(new MouseAdapter() {
        @Override
        public void mousePressed(MouseEvent e) {
            System.out.println("ispopup?: " + isShowingPopup);
            if (isShowingPopup) {
                showPopup = false;
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            showPopup = true;
        }
    });

Как правило, mousePressed вызывается до того, как меню теряет фокус, поэтому isShowingPopup отражает, отображалось ли всплывающее окно до нажатия кнопки. Затем, если меню было, мы просто устанавливаем showPopup на false, чтобы метод actionPerformed не отображал меню после его вызова (после отпускания мыши).

В каждом случае это происходило, как и ожидалось, за исключением одного: если меню показывалось, и пользователь нажимал кнопку мыши, но отпускал ее снаружи, actionPerformed никогда не вызывалось. Это означало, что showPopup оставалось ложным, и меню не отображалось при следующем нажатии кнопки. Чтобы это исправить, метод mouseReleased сбрасывает showPopup. Насколько я могу судить, метод mouseReleased вызывается после actionPerformed.

Я немного поиграл с полученной кнопкой, выполняя все, что мог придумать для кнопки, и она работала, как и ожидалось. Однако я не уверен на 100%, что события всегда будут происходить в одном и том же порядке.

В конечном счете, я думаю, что, по крайней мере, стоит попробовать.

1 голос
/ 11 марта 2010

Что вам нужно, это PopupMenuListener:

        menu.addPopupMenuListener(new PopupMenuListener() {

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {

            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
                System.out.println("MENU INVIS"); 
                isShowingPopup = false;     
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent arg0) {
                System.out.println("MENU CANCELLED"); 
                isShowingPopup = false;                     
            }
        });

Я вставил это в ваш код и убедился, что он работает.

1 голос
/ 11 марта 2010

Вы пытались добавить ComponentListener к JPopupMenu, чтобы вы знали, когда он был показан и скрыт (и соответственно обновили свой флаг isShowingPopup)? Я не уверен, что прислушиваться к изменениям фокуса - это всегда правильный подход.

1 голос
/ 11 марта 2010

Вы можете использовать JPopupMenu.isVisible () вместо логической переменной, чтобы проверить текущее состояние всплывающего меню.

0 голосов
/ 24 января 2013

Я попробовал Ответ Тихона Джелвиса (представляя умную комбинацию focusListener и mouseListener). Это не работает для меня в Linux (Java7 / GTK). : - (

Чтение http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 там написано "Обратите внимание, что использование этого метода не рекомендуется, потому что его поведение зависит от платформы."

Может случиться так, что порядок вызовов слушателя изменился с Java7 или изменился с GTK против Windows. Я бы не рекомендовал это решение, если вы хотите быть независимым от платформы.

Кстати: я создал новую учетную запись на stackoverflow, чтобы дать эту подсказку. Кажется, мне не разрешено комментировать его ответ (из-за репутации). Но, похоже, у меня есть кнопка для ее редактирования. Этот стекопоток очень забавная вещь. : -)

0 голосов
/ 11 марта 2010

Ну, я не могу быть уверен, не увидев весь ваш код, но возможно ли, что всплывающее окно вообще никогда не фокусируется? У меня были проблемы с тем, что в Swing у меня не было должного внимания, поэтому это может быть причиной. Попробуйте вызвать setFocusable(true) в меню, а затем вызвать requestFocus(), когда появится меню.

...