Summary

JTableでソート中のTableColumnの背景色などを変更してハイライト表示するよう設定します。

Source Code Examples

class ColumnHeaderRenderer implements TableCellRenderer {
  private static final Color SORTING_BGC = new Color(0xA4_CF_EF);

  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    TableCellRenderer r = table.getTableHeader().getDefaultRenderer();
    boolean b = isSortingColumn(table, column) || isSelected;
    Component c = r.getTableCellRendererComponent(
      table, value, b, hasFocus, row, column);
    if (b) {
      c.setBackground(SORTING_BGC);
    }
    return c;
  }

  private static boolean isSortingColumn(JTable table, int column) {
    return Optional.ofNullable(table.getRowSorter()).map(RowSorter::getSortKeys)
        .filter(keys -> !keys.isEmpty())
        .map(keys -> keys.get(0).getColumn())
        .map(i -> column == table.convertColumnIndexToView(i)).orElse(false);
  }
}
View in GitHub: Java, Kotlin

Explanation

  • HeaderRenderer
    • TableColumnがソート中の場合は選択状態で背景を描画するTableCellRendererを作成し、これをすべてのHeaderRendererTableColumn#setHeaderRenderer(...)で設定
    • NimbusLookAndFeelなどではデフォルトでソート中のTableColumnはハイライト描画され、上記のTableCellRendererで指定した背景色は無効
    • JTableでソート中のカラムセル色
    • JTableHeaderのハイライト表示
  • JLayer
    • JTableHeaderを配置する親JScrollPaneJLayerを設定してソート中のTableColumn領域を取得し、半透明の下線をハイライトとして描画
    • NimbusLookAndFeelなどでもJLayerで上書きしているのでハイライト色を指定可能
    • ソート中のTableColumn文字列を上書きしないよう、TableColumn全体ではなく下線のみ描画
    • ソート中のTableColumnがマウスドラッグで入れ替え中の場合は、TableColumnの位置をJTableHeader#getDraggedDistance()で補正する必要がある
    • TableColumnのドラッグによる順序変更が可能な領域を制限する
class SortingLayerUI extends LayerUI<JScrollPane> {
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

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

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer) {
      JLayer<?> layer = (JLayer<?>) c;
      Rectangle r = getTable(layer)
          .map(table -> getSortingColumnBounds(layer, table))
          .orElseGet(Rectangle::new);
      if (!r.isEmpty()) {
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setPaint(new Color(0x64_FE_AE_FF, true));
        g2.fill(r);
        g2.dispose();
      }
    }
  }

  @Override protected void processMouseEvent(
      MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseEvent(e, l);
    Component c = e.getComponent();
    if (c instanceof JTableHeader && e.getID() == MouseEvent.MOUSE_CLICKED) {
      c.repaint();
    }
  }

  @Override protected void processMouseMotionEvent(
      MouseEvent e, JLayer<? extends JScrollPane> l) {
    Component c = e.getComponent();
    if (c instanceof JTableHeader && e.getID() == MouseEvent.MOUSE_DRAGGED) {
      c.repaint();
    }
  }

  private static Optional<JTable> getTable(JLayer<?> layer) {
    return Optional.ofNullable(layer.getView())
        .filter(JScrollPane.class::isInstance)
        .map(JScrollPane.class::cast)
        .map(JScrollPane::getViewport)
        .map(JViewport::getView)
        .filter(JTable.class::isInstance)
        .map(JTable.class::cast);
  }

  private static int getSortingColumnIndex(JTable table) {
    return Optional.ofNullable(table.getRowSorter())
        .map(RowSorter::getSortKeys)
        .filter(keys -> !keys.isEmpty())
        .map(keys -> keys.get(0).getColumn())
        .map(table::convertColumnIndexToView)
        .orElse(-1);
  }

  private static Rectangle getSortingColumnBounds(JLayer<?> layer, JTable table) {
    Rectangle rect = new Rectangle();
    int sortingColumn = getSortingColumnIndex(table);
    if (sortingColumn >= 0) {
      Rectangle r = getSortingRect(table, sortingColumn);
      int h = r.height / 6;
      r.y += r.height - h;
      r.height = h;
      rect.setRect(SwingUtilities.convertRectangle(
        table.getTableHeader(), r, layer));
    }
    return rect;
  }

  private static Rectangle getSortingRect(JTable table, int sortingColumn) {
    JTableHeader header = table.getTableHeader();
    Rectangle r = header.getHeaderRect(sortingColumn);
    TableColumn draggedColumn = header.getDraggedColumn();
    if (draggedColumn != null) {
      int modelIndex = draggedColumn.getModelIndex();
      int viewIndex = table.convertColumnIndexToView(modelIndex);
      if (viewIndex == sortingColumn) {
        r.x += header.getDraggedDistance();
      }
    }
    return r;
  }
}

Reference

Comment