JButton からの JPopupMenu の表示/非表示。FocusListener が機能しない場合
-
19-09-2019 - |
質問
ドロップダウン スタイル メニューが付属した JButton が必要でした。そこで、JPopupMenu を取得し、以下のコードに示す方法で JButton にアタッチしました。行う必要があるのは次のとおりです。
- クリックされたときにポップアップを表示する
- 2回目にクリックすると非表示になります
- ポップアップで項目が選択されている場合は非表示にします
- ユーザーが画面内の別の場所をクリックした場合は非表示にします
これら 4 つは機能しますが、使用しているブール フラグのせいで、ユーザーが別の場所をクリックするか項目を選択すると、ボタンが再び表示される前にボタンを 2 回クリックする必要があります。そのため、この問題を修正するために FocusListener (まったく応答しません) を追加し、このような場合にフラグを false に設定しようとしました。
編集:回答投稿の最後の試み...
リスナーは次のとおりです。(これは JButton を拡張するクラス内にあるため、2 番目のリスナーは 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);
}
}
解決
これは、エレガントではないにしても、それほど悪くないハックであり、私の知る限りではうまくいく別のアプローチです。まず、一番上に、という 2 番目のブール値を追加しました。 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
boolean は他の場所では変更されません。フォーカスを取得した場合は表示されていると見なされ、フォーカスを失った場合は表示されていないと見なされます。
次に、 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
このメソッドは、呼び出されると (マウスを放した後) メニューを表示しません。
これは、1 つを除くすべてのケースで期待どおりに動作しました。メニューが表示されており、ユーザーがボタン上でマウスを押したが、ボタンの外で放した場合、 actionPerformed
決して呼ばれなかった。これはつまり、 showPopup
false のままで、次にボタンを押したときにメニューは表示されませんでした。これを修正するには、 mouseReleased
メソッドのリセット showPopup
. 。の mouseReleased
メソッドは後に呼び出されます actionPerformed
, 、 私の知る限り。
できあがったボタンを少し試してみて、ボタンに対して思いつく限りのことをすべて実行したところ、期待どおりに機能しました。ただし、イベントが常に同じ順序で発生するかどうかは 100% わかりません。
結局のところ、これは少なくとも試してみる価値があると思います。
他のヒント
ここで私はちょうど作られた琥珀Shahの「ビッグハック」の提案の変種です。 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%で動作します。
提案にオープン!
を使用できます JPopupMenu.isVisible() ブール変数の代わりに、ポップアップ メニューの現在の状態を確認します。
あなたはそれが示され、隠されていたときに知っている(とそれに応じてComponentListener
フラグを更新する)ようにしますが、JPopupMenu
にisShowingPopup
を追加しようとしたことがありますか?私は、フォーカスの変更をリッスンすることは必ずしも正しいアプローチではわからない。
あなたが必要なものの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;
}
});
私はあなたのコードにこれを挿入し、それが動作することを確認します。
まあ、私はあなたのコードのすべてを見ることなく確認することはできませんが、それはポップアップが実際にすべてのフォーカスを取得したことがないことがありますか?私は物事が前Swingで適切にフォーカスを得ていないとの問題を持っていたので、それは犯人である可能性があります。メニューのsetFocusable(true)
を呼び出した後、あなたは、メニューを表示させるときrequestFocus()
を呼び出してみます。
私は(のFocusListenerとのMouseListenerのスマートな組み合わせを導入)Tikhon Jelvisの回答を試みました。これは、Linux(Java7 / GTK)に私のために動作しません。 : - (
のhttp:/ /docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 のその振る舞いはプラットフォームであるため、このメソッドの使用は推奨されていることに注意してください」が書かれています依存。 "
これは、リスナーの呼び出しの順序はJava7で変更するか、Windowsの対GTKと変化している可能性があります。あなたは、プラットフォームに依存しないようにしたい場合、私はこのソリューションをお勧めしません。
ところで:私はこのヒントを与えるためにstackoverflowの上に新しいアカウントを作成しました。私が彼の答え(理由は評判の)にコメントすることは許されないのですようです。しかし、私がそれを編集するためのボタンを持っているようです。このstackoverflowのは非常に面白いものです。 : - )