概要

JTableの行選択背景が行全体でラウンド矩形になるよう先頭・末尾セルのTableCellRendererで角を丸めて描画します。

サンプルコード

class RoundSelectionRenderer extends DefaultTableCellRenderer {
  private static final double ARC = 6d;
  private Position pos;

  @Override public void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(getBackground().brighter());
    double w = getWidth() - 1d;
    double h = getHeight() - 1d;
    Area area = pos.getArea(w, h, ARC);
    g2.fill(area);
    if (isFocusable()) {
      g2.setColor(getBackground());
      g2.draw(area);
    }
    super.paintComponent(g);
    g2.dispose();
  }

  @Override public Component getTableCellRendererComponent(
      JTable table, Object value,
      boolean isSelected, boolean hasFocus,
      int row, int column) {
    Component c = super.getTableCellRendererComponent(
        table, value, isSelected, false, row, column);
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      l.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
      l.setOpaque(false);
      pos = getPosition(table, column);
      boolean b = value instanceof Number;
      l.setHorizontalAlignment(b ? RIGHT : LEFT);
    }
    c.setFocusable(hasFocus
        || table.getSelectionModel().getLeadSelectionIndex() == row);
    return c;
  }

  private static Position getPosition(JTable table, int column) {
    boolean isFirst = column == 0;
    boolean isLast = column == table.getColumnCount() - 1;
    Position p;
    if (isFirst) {
      p = Position.FIRST;
    } else if (isLast) {
      p = Position.LAST;
    } else {
      p = Position.MIDDLE;
    }
    return p;
  }

  private enum Position {
    FIRST, MIDDLE, LAST;

    public Area getArea(double w, double h, double arc) {
      Area area = new Area();
      if (this == FIRST) {
        area.add(new Area(new Rectangle2D.Double(w - arc, 0d, arc + arc, h)));
        area.add(new Area(new RoundRectangle2D.Double(0d, 0d, w, h, arc, arc)));
      } else if (this == LAST) {
        area.add(new Area(new Rectangle2D.Double(-arc, 0d, arc + arc, h)));
        area.add(new Area(new RoundRectangle2D.Double(0d, 0d, w, h, arc, arc)));
      } else {
        area.add(new Area(new Rectangle2D.Double(-arc, 0d, w + arc + arc, h)));
      }
      return area;
    }
  }
}
View in GitHub: Java, Kotlin

解説

  • JTable#setShowGrid(false)で罫線を非表示、JTable#setRowHeight(24)で行の高さを拡大、JTable#setIntercellSpacing(new Dimension(0, 3))で列の隙間を0に縮小、行の隙間を3に拡大して行選択描画が重ならないよう設定(Windows 11のファイルエクスプローラー風)
  • DefaultTableCellRenderer#paintComponent(...)をオーバーライドして先頭、末尾、それ以外のセルで選択背景の描画を変更
    • 先頭セル(column == 0)の場合は左端をRoundRectangle2D、右端をセル幅を右側方向にはみ出すRectangle2Dで作成したAreaで選択背景を描画
      • フォーカス用の罫線も上記のAreaを使用して描画
      • 右端の罫線はセル外で非表示になり、となりのセルと連続しているように表示される
    • 末尾セル(column == table.getColumnCount() - 1)の場合は右端をRoundRectangle2D、左端をセル幅を左側方向にはみ出すRectangle2Dで作成したAreaで選択背景を描画
      • 先頭セル同様、左端の罫線はセル外で非表示
    • それ以外の中間セルの場合そのセル領域を左右にはみ出すRectangle2Dを作成して選択背景として描画
      • 左右の罫線はセル外で非表示

参考リンク

コメント