Показ/сокрытие JPopupMenu от JButton; FocusListener, не работающий?

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

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

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

Править: Последняя попытка в сообщении ответа...

Вот слушатели: (Это находится в классе, расширяющем 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);
    }

}
8
задан kleopatra 15 December 2011 в 15:09
поделиться

6 ответов

Вот еще один подход, который не так уж плох для хака, если не элегантен, и который, насколько я могу судить, работает. Во-первых, в самом верху я добавил второе логическое значение 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
ответ дан 5 December 2019 в 21:17
поделиться

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

1
ответ дан 5 December 2019 в 21:17
поделиться

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

0
ответ дан 5 December 2019 в 21:17
поделиться

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

1
ответ дан 5 December 2019 в 21:17
поделиться

Что вам нужно, так это 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
ответ дан 5 December 2019 в 21:17
поделиться

Вот вариант предложения Эмбер Шах о «большом взломе», которое я только что сделал. Без флага 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% случаев.

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

3
ответ дан 5 December 2019 в 21:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: