Summary

JTabbedPaneの選択タブ切り替えをマウスプレスの直後ではなく、クリック完了後に変更します。

Source Code Examples

class TabClickLayerUI extends LayerUI<JTabbedPane> {
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JTabbedPane> l) {
    JTabbedPane tabs = l.getView();
    if (Objects.equals(tabs, e.getComponent())) {
      if (e.getID() == MouseEvent.MOUSE_CLICKED) {
        updateSelectedTab(tabs, e);
      } else if (e.getID() == MouseEvent.MOUSE_PRESSED) {
        e.consume();
      }
    }
  }

  private static void updateSelectedTab(JTabbedPane tabs, MouseEvent e) {
    int index = SwingUtilities.isLeftMouseButton(e)
        ? tabs.indexAtLocation(e.getX(), e.getY())
        : -1;
    if (index >= 0 && tabs.isEnabledAt(index)) {
      tabs.setSelectedIndex(index);
      if (tabs.isRequestFocusEnabled()) {
        tabs.requestFocus(FocusEvent.Cause.MOUSE_EVENT);
      }
    }
  }
}
View in GitHub: Java, Kotlin

Description

  • 上: デフォルト
    • JTabbedPaneのデフォルトはMetalLookAndFeel#createMouseListener(...)などで生成されたMouseListenermousePressedイベントを受信したとき、JTabbedPane#setSelectedIndex(index)を実行して選択タブが変更される
    • このため、単純にJTabbedPaneMouseListenerを追加してもMetalLookAndFeel#createMouseListener(...)で生成したMouseListenerが先に実行されて動作を変更できない
  • 中: JTabbedPane#setTabComponentAt(...)でタブコンポーネントをJButtonに変更
    • タブコンポーネントとしてJButtonを設定し、そのJButtonActionListenerを追加してマウスクリック後にJTabbedPane#setSelectedIndex(index)を実行する
    • タブコンポーネントのJButtonMouseMotionListenerを追加し、マウス移動イベントをJTabbedPane#dispatchEvent(...)で転送することでロールオーバー状態を更新している
    • タブコンポーネントのJButtonJButton#setInheritsPopupMenu(true)を設定してもJTabbedPane本体に設定したJPopupMenuを呼び出せない?
  • 下: JLayerでラップしてLayerUI#processMouseEvent(...)をオーバーライド
    • JTabbedPaneJLayerでラップしてマウスイベントを上書きすることで選択タブ切り替えの挙動を変更する
    • JTabbedPane全体をJLayerでラップするため、子のタブコンテンツ(このサンプルではJScrollPaneJTextArea)へのマウスイベントも上書きしてしまわないよう、JLayer#getView()で取得可能なラップされたJTabbedPaneMouseEvent#getComponent()で取得可能なイベント発生元のコンポーネントが同一の場合のみマウスイベントを上書きしている
      • MouseEvent.MOUSE_PRESSEDの場合はMouseEvent#consume()を実行して消費してMetalLookAndFeel#createMouseListener(...)で生成したMouseListenermousePressedを処理されないようにする
      • MouseEvent.MOUSE_CLICKEDの場合はJTabbedPane#setSelectedIndex(index)を実行して選択タブを切り替える
    • マウスプレス後にマウスカーソル位置を変更するとクリックイベントが発生しないので、これを変更する場合はMouseEvent.MOUSE_RELEASEDが対象タブ領域内で発生したら選択タブを切り替えるよう修正する必要がある

Reference

Comment