概要

JTableHeaderの任意の列のソートアイコンをtableタグを使用してヘッダセルの左上に表示するよう設定します。

スクリーンショット

Swing/SortIconLayoutHeaderRenderer.png

サンプルコード

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;
  }
}
View in GitHub: Java, Kotlin

解説

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

参考リンク

コメント