• 追加された行はこの色です。
  • 削除された行はこの色です。
---
category: swing
folder: SortIconLayoutHeaderRenderer
title: JTableHeaderのソートアイコンをヘッダセルの左上に表示する
tags: [JTable, JTableHeader, TableCellRenderer, TableRowSorter]
author: aterai
pubdate: 2022-09-19T14:21:23+09:00
description: JTableHeaderの任意の列のソートアイコンをtableタグを使用してヘッダセルの左上に表示するよう設定します。
image: https://drive.google.com/uc?id=1f31_xhhURzzecQrFMY_jHDhCwvXngzFa
---
* 概要 [#summary]
JTableHeaderの任意の列のソートアイコンをtableタグを使用してヘッダセルの左上に表示するよう設定します。
`JTableHeader`の任意の列のソートアイコンを`table`タグを使用してヘッダセルの左上に表示するよう設定します。

#download(https://drive.google.com/uc?id=1f31_xhhURzzecQrFMY_jHDhCwvXngzFa)

* サンプルコード [#sourcecode]
#code(link){{
class SortIconLayoutHeaderRenderer implements TableCellRenderer {
  private static final String ASCENDING = "Table.ascendingSortIcon";
  private static final String DESCENDING = "Table.descendingSortIcon";
  private final URI ascendingUri;
  private final URI descendingUri;
  private final URI naturalUri;
  private final Icon ascendingIcon;
  private final Icon descendingIcon;
  private final EmptyIcon emptyIcon;

  protected SortIconLayoutHeaderRenderer() {
    ascendingIcon = UIManager.getLookAndFeelDefaults().getIcon(ASCENDING);
    ascendingUri = getIconUri(ascendingIcon);
    descendingIcon = UIManager.getLookAndFeelDefaults().getIcon(DESCENDING);
    descendingUri = getIconUri(descendingIcon);
    emptyIcon = new EmptyIcon();
    naturalUri = getIconUri(emptyIcon);
  }

  @Override public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus,
        int row, int column) {
    emptyIcon.width = ascendingIcon.getIconWidth();
    emptyIcon.height = ascendingIcon.getIconHeight();
    UIManager.put(ASCENDING, emptyIcon);
    UIManager.put(DESCENDING, emptyIcon);
    TableCellRenderer r = table.getTableHeader().getDefaultRenderer();
    Component c = r.getTableCellRendererComponent(
        table, value, isSelected, hasFocus, row, column);
    UIManager.put(ASCENDING, ascendingIcon);
    UIManager.put(DESCENDING, descendingIcon);
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      // l.setHorizontalAlignment(SwingConstants.RIGHT);
      URI sortUri = null;
      SortOrder sortOrder = getColumnSortOrder(table, column);
      switch (sortOrder) {
        case ASCENDING:
          sortUri = ascendingUri;
          break;
        case DESCENDING:
          sortUri = descendingUri;
          break;
        default: // case UNSORTED:
          sortUri = naturalUri;
          break;
      }
      int v = 10;
      String img = String.format("<img src='%s'>", sortUri);
      String pct = String.format("<td align='right'>%d%%", v);
      String fmt = "<html><table><tr><td>%s%s<tr><td><td align='right'>%s";
      l.setText(String.format(fmt, img, pct, Objects.toString(value, "")));
    }
    return c;
  }

  public static SortOrder getColumnSortOrder(JTable table, int column) {
    SortOrder rv = SortOrder.UNSORTED;
    if (table != null && table.getRowSorter() != null) {
      List<? extends RowSorter.SortKey> sortKeys = table.getRowSorter().getSortKeys();
      int mi = table.convertColumnIndexToModel(column);
      if (!sortKeys.isEmpty() && sortKeys.get(0).getColumn() == mi) {
        rv = sortKeys.get(0).getSortOrder();
      }
    }
    return rv;
  }

  public static URI getIconUri(Icon icon) {
    int w = icon.getIconWidth();
    int h = icon.getIconHeight();
    BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = img.createGraphics();
    icon.paintIcon(null, g2, 0, 0);
    g2.dispose();
    try {
      File tmp = File.createTempFile("icon", ".png");
      tmp.deleteOnExit();
      ImageIO.write(img, "png", tmp);
      return tmp.toURI();
    } catch (IOException ex) {
      UIManager.getLookAndFeel().provideErrorFeedback(null);
      ex.printStackTrace();
    }
    return null;
  }
}
}}

* 解説 [#explanation]
- `JTable#getColumnModel().getColumn(index)#setHeaderRenderer(...)`で`index`列目のヘッダレンダラーをラップし、ヘッダ文字列内に`html`タグを使用してソートアイコンを描画するよう設定
- 元のソートアイコンは`JTable#getTableHeader()#getDefaultRenderer()#getTableCellRendererComponent(...)`でヘッダ描画用コンポーネントを取得する直前に空アイコンを`UIManager.put("Table.ascendingSortIcon", emptyIcon)`などで非表示に設定、ヘッダ描画用コンポーネントを取得後に`UIManager.put("Table.ascendingSortIcon", UIManager.getLookAndFeelDefaults().getIcon("Table.ascendingSortIcon"))`で`LookAndFeel`デフォルトのソートアイコンに復元
-- `TableCellRenderer#getTableCellRendererComponent(...)`内で上記の一時的なソートアイコンの置換を実行しないと指定した列以外のソートアイコンも非表示になる
-- `DefaultTableCellHeaderRenderer`を継承する`WindowsTableHeaderUI.XPDefaultRenderer`は`paint(...)`をオーバーライドしてヘッダセルを描画しているので`LayoutManager`を設定したり`JLayer`でアイコンを上書きするとフォーカス背景色などが描画されなくなる
- `html`タグで`Icon`を直接指定する方法がないので一旦ソートアイコンを`ImageIO.write(...)`で一時ファイルに書き出し、`File.toURI()`で`URI`に変換してから`<img src='file:/C:/Users/.../AppData/Local/Temp/icon1056940121138232542.png'>`のようなタグを生成して使用している

* 参考リンク [#reference]
- [[JTableのソートアイコンを変更>Swing/TableSortIcon]]
- [[JTableHeaderのソートアイコンをヘッダセル右揃えで表示する>Swing/TableHeaderRightAlignSortArrow]]

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