---
category: swing
folder: HyperlinkHeaderCellRenderer
title: JTableHeaderをリンク風に表示しセル内余白のクリックを無効にする
tags: [JTableHeader, JTable, TableCellRenderer]
author: aterai
pubdate: 2022-07-25T03:34:19+09:00
description: JTableHeaderの列文字列をリンク風に表示し、そのリンク以外のセル内余白をクリックしても列ソートを実行しないよう設定します。
image: https://drive.google.com/uc?id=15xkCkHGxacN_pZ9_LxvArcHbHTdXsA1B
---
* 概要 [#summary]
`JTableHeader`の列文字列をリンク風に表示し、そのリンク以外のセル内余白をクリックしても列ソートを実行しないよう設定します。

#download(https://drive.google.com/uc?id=15xkCkHGxacN_pZ9_LxvArcHbHTdXsA1B)

* サンプルコード [#sourcecode]
#code(link){{
class HyperlinkHeaderCellRenderer extends DefaultTableCellRenderer implements MouseInputListener {
  private final Border border = BorderFactory.createCompoundBorder(
      BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY),
      BorderFactory.createEmptyBorder(4, 1, 3, 2));
  private final Color alphaZero = new Color(0x0, true);
  private int col = -1;

  @Override public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    String str = Objects.toString(value, "");
    String sort = "";
    RowSorter<? extends TableModel> sorter = table.getRowSorter();
    if (Objects.nonNull(sorter) && !sorter.getSortKeys().isEmpty()) {
      RowSorter.SortKey sortKey = sorter.getSortKeys().get(0);
      if (column == sortKey.getColumn()) {
        String k = sortKey.getSortOrder() == SortOrder.ASCENDING ? "?" : "?";
        String k = sortKey.getSortOrder() == SortOrder.ASCENDING ? "▴" : "▾";
        sort = "<small>" + k;
      }
    }
    Component c = super.getTableCellRendererComponent(
        table, value, isSelected, hasFocus, row, column);
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      if (this.col == column) {
        l.setText("<html><u><font color='blue'>" + str + "</u>" + sort);
      } else if (hasFocus) {
        l.setText("<html><font color='blue'>" + str + sort);
      } else {
        l.setText("<html>" + str + sort);
      }
      l.setHorizontalAlignment(SwingConstants.LEADING);
      l.setOpaque(false);
      l.setBackground(alphaZero);
      l.setForeground(Color.BLACK);
      l.setBorder(border);
    }
    return c;
  }

  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 24;
    return d;
  }

  private Rectangle getTextRect(JTableHeader header, int idx) {
    JTable table = header.getTable();
    TableCellRenderer hr = table.getTableHeader().getDefaultRenderer();
    Object headerValue = header.getColumnModel().getColumn(idx).getHeaderValue();
    String str = Objects.toString(headerValue, "");
    Component c = hr.getTableCellRendererComponent(table, headerValue, false, true, 0, idx);
    Rectangle viewRect = new Rectangle(header.getHeaderRect(idx));
    Rectangle iconRect = new Rectangle();
    Rectangle textRect = new Rectangle();
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      Insets ins = l.getInsets();
      viewRect.x += ins.left;
      viewRect.width -= ins.left + ins.right;
      SwingUtilities.layoutCompoundLabel(
          header,
          header.getFontMetrics(header.getFont()),
          str,
          l.getIcon(),
          l.getVerticalAlignment(),
          l.getHorizontalAlignment(),
          l.getVerticalTextPosition(),
          l.getHorizontalTextPosition(),
          viewRect,
          iconRect,
          textRect,
          l.getIconTextGap());
    }
    return textRect;
  }

  @Override public void mouseMoved(MouseEvent e) {
    JTableHeader header = (JTableHeader) e.getComponent();
    int ci = header.columnAtPoint(e.getPoint());
    col = getTextRect(header, ci).contains(e.getPoint()) ? ci : -1;
    header.repaint(header.getHeaderRect(ci));
  }

  @Override public void mouseExited(MouseEvent e) {
    col = -1;
    e.getComponent().repaint();
  }

  @Override public void mouseClicked(MouseEvent e) {
    JTableHeader header = (JTableHeader) e.getComponent();
    JTable table = header.getTable();
    int ci = header.columnAtPoint(e.getPoint());
    int idx = table.convertColumnIndexToModel(ci);
    if (getTextRect(header, ci).contains(e.getPoint())) {
      RowSorter<?> sorter = table.getRowSorter();
      if (sorter instanceof DefaultRowSorter) {
        ((DefaultRowSorter<?, ?>) sorter).setSortable(idx, true);
        sorter.toggleSortOrder(idx);
        ((DefaultRowSorter<?, ?>) sorter).setSortable(idx, false);
      }
    }
  }

  @Override public void mouseDragged(MouseEvent e) {
    /* not needed */
  }

  @Override public void mouseEntered(MouseEvent e) {
    /* not needed */
  }

  @Override public void mousePressed(MouseEvent e) {
    /* not needed */
  }

  @Override public void mouseReleased(MouseEvent e) {
    /* not needed */
  }
}
}}

* 解説 [#explanation]
- `TableCellRenderer#getTableCellRendererComponent(...)`をオーバーライド
-- マウスカーソルが列文字列上に存在する場合は`html`の`<u>`タグで列文字列下線を引く
--- [[JTableのセル内でリンクだけHover可能にする>Swing/PointInsidePrefSize]]
-- ソートキー状態も列文字列の末尾に`<small>?`などを追加して表示
-- ソートキー状態も列文字列の末尾に`<small>▾`などを追加して表示
--- [[JTableの複数キーを使ったソートをヘッダに表示する>Swing/MultisortHeaderRenderer]]
- `MouseInputListener#mouseClicked(...)`をオーバーライド
-- 初期状態ではすべての列のソートを無効化し、列文字列がクリックされた場合のみその列のソートを可能にしてから`RowSorter#toggleSortOrder(idx)`でソートし、直後にまた列をソート不可に設定
- `MouseInputListener#mouseMoved(...)`をオーバーライド
-- マウスカーソルが列文字列上に存在する場合はその列番号を設定して列ヘッダを再描画
- `SwingUtilities.layoutCompoundLabel(...)`
-- マウスカーソルが列文字列上に存在するかどうかは`SwingUtilities.layoutCompoundLabel(...)`メソッドで取得した文字列領域を使用して判断する
-- `DefaultTableCellRenderer`が`JLabel`を継承しているので`SwingUtilities.layoutCompoundLabel(...)`メソッドで文字列領域が取得可能

* 参考リンク [#reference]
- [[JTableのセルにHyperlinkを表示>Swing/HyperlinkInTableCell]]
- [[JTableのセル内でリンクだけHover可能にする>Swing/PointInsidePrefSize]]
- [[JTableのヘッダを透明化>Swing/TransparentTableHeader]]
- [[JTableの複数キーを使ったソートをヘッダに表示する>Swing/MultisortHeaderRenderer]]

* コメント [#comment]
#comment
#comment