Мне был нужен 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);
}
}
Вот еще один подход, который не так уж плох для хака, если не элегантен, и который, насколько я могу судить, работает. Во-первых, в самом верху я добавил второе логическое значение 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%, что события всегда будут происходить в одном и том же порядке.
В конце концов, я думаю, что это, по крайней мере, стоит попробовать.
Вы пробовали добавить ComponentListener
в JPopupMenu
, чтобы вы знали, когда он был показан и скрыт (и соответствующим образом обновите свой флаг isShowingPopup
)? Я не уверен, что прислушиваться к изменениям фокуса - это обязательно правильный подход.
Что ж, я не могу быть уверенным, не увидев весь ваш код, но возможно ли, что всплывающее окно вообще никогда не фокусируется? Раньше у меня были проблемы с неправильным фокусированием в Swing, так что это могло быть виновником. Попробуйте вызвать setFocusable (true)
в меню, а затем вызвать requestFocus ()
, когда отобразите меню.
Вы можете использовать JPopupMenu.isVisible () вместо логической переменной для проверки текущего состояния всплывающего меню.
Что вам нужно, так это 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;
}
});
Я вставил это в ваш код и проверил, что он работает.
Вот вариант предложения Эмбер Шах о «большом взломе», которое я только что сделал. Без флага 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% случаев.
Открыт для предложений!