---
category: swing
folder: RowGroupInTable
title: JTableで同一内容のセルを空表示にしてグループ化を表現する
tags: [JTable, TableRowSorter, TableCellRenderer]
author: aterai
pubdate: 2017-06-05T16:09:45+09:00
description: JTableで直上のセルと同一内容のセルを空表示にして、行のグループ化を表現します。
image: https://drive.google.com/uc?id=1BtPc00VYQd5w5UwghwQc6KYrIoZCZ0W_EQ
---
* 概要 [#summary]
`JTable`で直上のセルと同一内容のセルを空表示にして、行のグループ化を表現します。

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

* サンプルコード [#sourcecode]
#code(link){{
String colors = "colors";
String sports = "sports";
String food   = "food";
addRowData(model, new RowData(colors, "blue",     1));
addRowData(model, new RowData(colors, "violet",   2));
addRowData(model, new RowData(colors, "red",      3));
addRowData(model, new RowData(colors, "yellow",   4));
String food = "food";
addRowData(model, new RowData(colors, "blue", 1));
addRowData(model, new RowData(colors, "violet", 2));
addRowData(model, new RowData(colors, "red", 3));
addRowData(model, new RowData(colors, "yellow", 4));
addRowData(model, new RowData(sports, "baseball", 23));
addRowData(model, new RowData(sports, "soccer",   22));
addRowData(model, new RowData(sports, "soccer", 22));
addRowData(model, new RowData(sports, "football", 21));
addRowData(model, new RowData(sports, "hockey",   20));
addRowData(model, new RowData(food,   "hot dogs", 10));
addRowData(model, new RowData(food,   "pizza",    11));
addRowData(model, new RowData(food,   "ravioli",  12));
addRowData(model, new RowData(food,   "bananas",  13));
addRowData(model, new RowData(sports, "hockey", 20));
addRowData(model, new RowData(food, "hot dogs", 10));
addRowData(model, new RowData(food, "pizza", 11));
addRowData(model, new RowData(food, "ravioli", 12));
addRowData(model, new RowData(food, "bananas", 13));
// ...
class RowDataRenderer implements TableCellRenderer {
  private final TableCellRenderer renderer = new DefaultTableCellRenderer();
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    JLabel label = (JLabel) renderer.getTableCellRendererComponent(
        table, value, isSelected, hasFocus, row, column);
    label.setHorizontalAlignment(SwingConstants.LEFT);
    RowData data = (RowData) value;
    switch (table.convertColumnIndexToModel(column)) {
    case 0:
      String str = data.getGroup();
      if (row > 0) {
        RowData prev = (RowData) table.getValueAt(row - 1, column);
        if (Objects.equals(prev.getGroup(), str)) {
          label.setText(" ");
          break;
        }
      }
      label.setText("+ " + str);
      break;
    case 1:
      label.setText(data.getName());
      break;
    case 2:
      label.setHorizontalAlignment(SwingConstants.RIGHT);
      label.setText(Integer.toString(data.getCount()));
      break;
    default:
      break;
    }
    return label;
  }
}
}}

* 解説 [#explanation]
- `TableModel`
-- データとしてすべての列に`RowData`のインスタンスのコピーを設定
-- セルレンダラーで第`0`列目に`RowData.getGroup()`の値、第`1`列目に`RowData.getName()`の値、第`2`列目に`RowData.getCount()`の値を表示するよう設定
- `RowDataRenderer`
-- `JTable`の第`0`列目で表示上の直上のセルと`RowData.getGroup()`で取得した値が同一の場合、その値ではなく空白文字を表示するよう設定
--- 表示上の`0`行目には直上セルは存在しないので、常にそのまま`RowData.getGroup()`の値を表示する
- `TableRowSorter`
-- ソートでグループ化が変化しないようにグループ化しない第`1`列目と第`2`列目は、まず第`0`列目をソートしてからその列の比較を行う以下のような`Comparator`を使用する
#code{{
TableRowSorter<TableModel> sorter = new TableRowSorter<>(table.getModel());
Comparator<RowData> c = Comparator.comparing(RowData::getGroup);
sorter.setComparator(0, c);
sorter.setComparator(1, c.thenComparing(RowData::getName));
sorter.setComparator(2, c.thenComparing(RowData::getCount));
table.setRowSorter(sorter);
}}

* 参考リンク [#reference]
- [https://stackoverflow.com/questions/43011596/swing-sort-a-jtables-rows-but-keep-them-grouped-by-one-column-and-only-show-t java - Swing - sort a JTable's rows but keep them grouped by one column and only show the value once on top of each group - Stack Overflow]
- [[JTableでファイルとディレクトリを別々にソート>Swing/FileDirectoryComparator]]

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