• category: swing folder: FocusBeforePopup title: JPopupMenuを開く前に対象となるJTextFieldにFocusを移動する tags: [JPopupMenu, JTextField, Focus, JTextComponent] author: aterai pubdate: 2017-11-06T15:23:38+09:00 description: JTextFieldなどに設定したJPopupMenuをマウスの右クリックで開くとき、そのにFocusを移動し文字列を全選択します。 image: https://drive.google.com/uc?export=view&id=1DYqevQ-Nj2i5IptiAWC7KPYVKcbmuL9sMA

概要

JTextFieldなどに設定したJPopupMenuをマウスの右クリックで開くとき、そのJTextComponentFocusを移動し文字列を全選択します。

サンプルコード

class TextComponentPopupMenu extends JPopupMenu {
  private final Action cutAction = new DefaultEditorKit.CutAction();
  private final Action copyAction = new DefaultEditorKit.CopyAction();
  private final Action pasteAction = new DefaultEditorKit.PasteAction();
  protected TextComponentPopupMenu() {
    super();
    add(cutAction);
    add(copyAction);
    add(pasteAction);
  }
  @Override public void show(Component c, int x, int y) {
    System.out.println(c.getClass().getName() + ": " + c.getName());
    if (c instanceof JTextComponent) {
      JTextComponent tc = (JTextComponent) c;
      tc.requestFocusInWindow();
      boolean isSelected = tc.getSelectionStart() != tc.getSelectionEnd();
      if (tc instanceof JTextField && !tc.isFocusOwner() && !isSelected) {
        tc.selectAll();
        isSelected = true;
      }
      cutAction.setEnabled(isSelected);
      copyAction.setEnabled(isSelected);
      super.show(c, x, y);
    }
  }
}
View in GitHub: Java, Kotlin

解説

  • Default setComponentPopupMenu
    • JTextField#setComponentPopupMenu(...)JTextFieldJPopupMenuを設定
    • 別のJTextComponentにフォーカスがある状態で、このJTextField内を右クリックしてJPopupMenuを表示してもフォーカスは前のJTextComponentに残る
    • new DefaultEditorKit.PasteAction()で生成した貼り込みアクションなどは、フォーカスのあるJTextComponentに対して実行されるので、前のJTextComponentに文字列が張り込まれる
  • Override JPopupMenu#show(...)
    • JTextField#setComponentPopupMenu(...)JTextFieldJPopupMenuを設定
    • JPopupMenu#show(...)をオーバーライドしてJPopupMenuを開く前にComponent#requestFocusInWindow()メソッドを実行し、このJTextFieldにフォーカスを移動する
      • PopupMenuListener#popupMenuWillBecomeVisible(...)、またはMouseListener#mousePressed(...)をオーバーライドする方法でも構わない
    • フォーカス移動と合わせて、JTextField内を右クリックしてJPopupMenuを開く場合は内部の文字列を全選択する処理を追加

  • JPopupMenu does not open???
    • 編集可能に設定したJComboBoxJTextFieldにマウスクリックでフォーカスを移動しても、他コンポーネントのJPopupMenuが開いたままになるバグ(仕様?)があるため、JTextField#setComponentPopupMenu(...)で追加したJPopupMenuを開くことができない
    • JDK-8044493 Clicking on an editable JComboBox leaves JPopupMenus and other menus open - Java Bug System
    • Prevent popup menu dismissal | Exploding Pixels
      • textField.putClientProperty("doNotCancelPopup", null);で回避可能(ただしエディタをマウスでクリックするとJComboBoxのドロップダウンリストも閉じるようになる)
        JComboBox<String> combo5 = new JComboBox<>(new String[] {"000", "111", "222"});
        combo5.setEditable(true);
        combo5.setComponentPopupMenu(popup2);
        JTextField textField5 = (JTextField) combo5.getEditor().getEditorComponent();
        textField5.putClientProperty("doNotCancelPopup", null);
        
  • addMouseListener
    • 編集可能に設定したJComboBoxJTextFieldMouseListenerを追加し、マウスでクリックされたら一旦すべてのJPopupMenuを閉じるよう設定
JComboBox<String> combo4 = new JComboBox<>(new String[] {"addMouseListener", "111", "222"});
combo4.setEditable(true);
JTextField textField4 = (JTextField) combo4.getEditor().getEditorComponent();
textField4.setComponentPopupMenu(popup2);
textField4.setName("textField4");
textField4.addMouseListener(new MouseAdapter() {
  @Override public void mousePressed(MouseEvent e) {
    System.out.println("Close all JPopUpMenu");
    // https://ateraimemo.com/Swing/GetAllPopupMenus.html
    for (MenuElement m : MenuSelectionManager.defaultManager().getSelectedPath()) {
      if (combo4.isPopupVisible()) {
        continue;
      } else if (m instanceof JPopupMenu) {
        ((JPopupMenu) m).setVisible(false);
      }
    }
  }
});

参考リンク

コメント