概要

JTableHeaderの列文字列をリンク風に表示し、そのリンク以外のセル内余白をクリックしても列ソートを実行しないよう設定します。

スクリーンショット

Swing/HyperlinkHeaderCellRenderer.png

サンプルコード

class HyperlinkHeaderCellRenderer extends DefaultTableCellRenderer implements MouseInputListener {
  private final Border border = BorderFactory.createCompoundBorder(
      BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY),
      BorderFactory.createEmptyBorder(4, 1, 3, 2));
  private final Color alphaZero = new Color(0x0, true);
  private int col = -1;

  @Override public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    String str = Objects.toString(value, "");
    String sort = "";
    RowSorter<? extends TableModel> sorter = table.getRowSorter();
    if (Objects.nonNull(sorter) && !sorter.getSortKeys().isEmpty()) {
      RowSorter.SortKey sortKey = sorter.getSortKeys().get(0);
      if (column == sortKey.getColumn()) {
        String k = sortKey.getSortOrder() == SortOrder.ASCENDING ? "▴" : "▾";
        sort = "<small>" + k;
      }
    }
    Component c = super.getTableCellRendererComponent(
        table, value, isSelected, hasFocus, row, column);
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      if (this.col == column) {
        l.setText("<html><u><font color='blue'>" + str + "</u>" + sort);
      } else if (hasFocus) {
        l.setText("<html><font color='blue'>" + str + sort);
      } else {
        l.setText("<html>" + str + sort);
      }
      l.setHorizontalAlignment(SwingConstants.LEADING);
      l.setOpaque(false);
      l.setBackground(alphaZero);
      l.setForeground(Color.BLACK);
      l.setBorder(border);
    }
    return c;
  }

  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.height = 24;
    return d;
  }

  private Rectangle getTextRect(JTableHeader header, int idx) {
    JTable table = header.getTable();
    TableCellRenderer hr = table.getTableHeader().getDefaultRenderer();
    Object headerValue = header.getColumnModel().getColumn(idx).getHeaderValue();
    String str = Objects.toString(headerValue, "");
    Component c = hr.getTableCellRendererComponent(table, headerValue, false, true, 0, idx);
    Rectangle viewRect = new Rectangle(header.getHeaderRect(idx));
    Rectangle iconRect = new Rectangle();
    Rectangle textRect = new Rectangle();
    if (c instanceof JLabel) {
      JLabel l = (JLabel) c;
      Insets ins = l.getInsets();
      viewRect.x += ins.left;
      viewRect.width -= ins.left + ins.right;
      SwingUtilities.layoutCompoundLabel(
          header,
          header.getFontMetrics(header.getFont()),
          str,
          l.getIcon(),
          l.getVerticalAlignment(),
          l.getHorizontalAlignment(),
          l.getVerticalTextPosition(),
          l.getHorizontalTextPosition(),
          viewRect,
          iconRect,
          textRect,
          l.getIconTextGap());
    }
    return textRect;
  }

  @Override public void mouseMoved(MouseEvent e) {
    JTableHeader header = (JTableHeader) e.getComponent();
    int ci = header.columnAtPoint(e.getPoint());
    col = getTextRect(header, ci).contains(e.getPoint()) ? ci : -1;
    header.repaint(header.getHeaderRect(ci));
  }

  @Override public void mouseExited(MouseEvent e) {
    col = -1;
    e.getComponent().repaint();
  }

  @Override public void mouseClicked(MouseEvent e) {
    JTableHeader header = (JTableHeader) e.getComponent();
    JTable table = header.getTable();
    int ci = header.columnAtPoint(e.getPoint());
    int idx = table.convertColumnIndexToModel(ci);
    if (getTextRect(header, ci).contains(e.getPoint())) {
      RowSorter<?> sorter = table.getRowSorter();
      if (sorter instanceof DefaultRowSorter) {
        ((DefaultRowSorter<?, ?>) sorter).setSortable(idx, true);
        sorter.toggleSortOrder(idx);
        ((DefaultRowSorter<?, ?>) sorter).setSortable(idx, false);
      }
    }
  }

  @Override public void mouseDragged(MouseEvent e) {
    /* not needed */
  }

  @Override public void mouseEntered(MouseEvent e) {
    /* not needed */
  }

  @Override public void mousePressed(MouseEvent e) {
    /* not needed */
  }

  @Override public void mouseReleased(MouseEvent e) {
    /* not needed */
  }
}
View in GitHub: Java, Kotlin

解説

  • TableCellRenderer#getTableCellRendererComponent(...)をオーバーライド
  • MouseInputListener#mouseClicked(...)をオーバーライド
    • 初期状態ではすべての列のソートを無効化し、列文字列がクリックされた場合のみその列のソートを可能にしてからRowSorter#toggleSortOrder(idx)でソートし、直後にまた列をソート不可に設定
  • MouseInputListener#mouseMoved(...)をオーバーライド
    • マウスカーソルが列文字列上に存在する場合はその列番号を設定して列ヘッダを再描画
  • SwingUtilities.layoutCompoundLabel(...)
    • マウスカーソルが列文字列上に存在するかどうかはSwingUtilities.layoutCompoundLabel(...)メソッドで取得した文字列領域を使用して判断する
    • DefaultTableCellRendererJLabelを継承しているのでSwingUtilities.layoutCompoundLabel(...)メソッドで文字列領域が取得可能

参考リンク

コメント