• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JTableのセルを横方向に連結する
#navi(../)
#tags(JTable, TableCellRenderer, JTextArea, JScrollPane)
RIGHT:Posted by &author(aterai); at 2013-05-13
*JTableのセルを横方向に連結する [#y3d3b7a0]
``JTable``のセルを横方向に連結するセルレンダラーを作成します。
---
category: swing
folder: ColumnSpanningCellRenderer
title: JTableのセルを横方向に連結する
tags: [JTable, TableCellRenderer, JTextArea, JScrollPane]
author: aterai
pubdate: 2013-05-13T16:59:22+09:00
description: JTableのセルを横方向に連結するセルレンダラーを作成します。
image: https://lh5.googleusercontent.com/-wcXag_bBidU/UY-uA3riCRI/AAAAAAAABrs/Q_V-fdNVRu8/s800/ColumnSpanningCellRenderer.png
hreflang:
    href: https://java-swing-tips.blogspot.com/2013/05/column-spanning-tablecellrenderer.html
    lang: en
---
* 概要 [#summary]
`JTable`のセルを横方向に連結するセルレンダラーを作成します。

-&jnlp;
-&jar;
-&zip;
#download(https://lh5.googleusercontent.com/-wcXag_bBidU/UY-uA3riCRI/AAAAAAAABrs/Q_V-fdNVRu8/s800/ColumnSpanningCellRenderer.png)

//#screenshot
#ref(https://lh5.googleusercontent.com/-wcXag_bBidU/UY-uA3riCRI/AAAAAAAABrs/Q_V-fdNVRu8/s800/ColumnSpanningCellRenderer.png)

**サンプルコード [#j27fd87a]
* サンプルコード [#sourcecode]
#code(link){{
class ColumnSpanningCellRenderer extends JPanel implements TableCellRenderer{
class ColumnSpanningCellRenderer extends JPanel implements TableCellRenderer {
  private static final int TARGET_COLIDX = 0;
  private final JTextArea textArea = new JTextArea(2, 999999);
  private final JLabel label = new JLabel();
  private final JLabel iconLabel = new JLabel();
  private final JScrollPane scroll = new JScrollPane();
  private final JScrollPane scroll = new JScrollPane(textArea);

  public ColumnSpanningCellRenderer() {
    super(new BorderLayout(0, 0));
  protected ColumnSpanningCellRenderer() {
    super(new BorderLayout());

    scroll.setViewportView(textArea);
    scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
    scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
    scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    scroll.setBorder(BorderFactory.createEmptyBorder());
    scroll.setViewportBorder(BorderFactory.createEmptyBorder());
    scroll.setOpaque(false);
    scroll.getViewport().setOpaque(false);

    textArea.setBorder(BorderFactory.createEmptyBorder());
    textArea.setMargin(new Insets(0, 0, 0, 0));
    textArea.setForeground(Color.RED);
    textArea.setEditable(false);
    textArea.setFocusable(false);
    textArea.setOpaque(false);

    iconLabel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
    iconLabel.setOpaque(false);

    Border b1 = BorderFactory.createEmptyBorder(2, 2, 2, 2);
    Border b2 = BorderFactory.createMatteBorder(0, 0, 1, 1, Color.GRAY);
    label.setBorder(BorderFactory.createCompoundBorder(b2, b1));

    setBackground(textArea.getBackground());
    setOpaque(true);
    add(label, BorderLayout.NORTH);
    add(scroll);
  }

  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected,
      boolean hasFocus, int row, int column) {
    Test test;
    if(value instanceof Test) {
      test = (Test)value;
    OptionPaneDescription d;
    if (value instanceof OptionPaneDescription) {
      d = (OptionPaneDescription) value;
      add(iconLabel, BorderLayout.WEST);
    }else{
    } else {
      String title = Objects.toString(value, "");
      int mrow = table.convertRowIndexToModel(row);
      String title = value!=null ? value.toString() : "";
      Test t = (Test)table.getModel().getValueAt(mrow, 0);
      String text = t!=null ? t.text : "";
      Icon icon = t!=null ? t.icon : null;
      test = new Test(title, icon, text);
      Object o = table.getModel().getValueAt(mrow, 0);
      if (o instanceof OptionPaneDescription) {
        OptionPaneDescription t = (OptionPaneDescription) o;
        d = new OptionPaneDescription(title, t.icon, t.text);
      } else {
        d = new OptionPaneDescription(title, null, "");
      }
      remove(iconLabel);
    }
    label.setText(test.title);
    textArea.setText(test.text);
    iconLabel.setIcon(test.icon);
    label.setText(d.title);
    textArea.setText(d.text);
    iconLabel.setIcon(d.icon);

    Rectangle cr = table.getCellRect(row, column, false);
/*/ //Flickering on first visible row ?
    if(column==0) {
      cr.x = 0;
      cr.width -= iconLabel.getPreferredSize().width;
    }else{
    if (column != TARGET_COLIDX) {
      cr.x -= iconLabel.getPreferredSize().width;
    }
    textArea.scrollRectToVisible(cr);
/*/
    if(column!=0) cr.x -= iconLabel.getPreferredSize().width;
    scroll.getViewport().setViewPosition(cr.getLocation());
//*/
    if(isSelected) {

    if (isSelected) {
      setBackground(Color.ORANGE);
    }else{
    } else {
      setBackground(Color.WHITE);
    }
    return this;
  }
}

class OptionPaneDescription {
  public final String title;
  public final Icon icon;
  public final String text;
  protected OptionPaneDescription(String title, Icon icon, String text) {
    this.title = title;
    this.icon  = icon;
    this.text  = text;
  }
}
}}

**解説 [#m5397014]
文字列を配置した``JTextArea``を各カラムごとに``JViewport``で表示する領域を切り取ってセルに貼り付けています。さらに、``JTable``のセルの縦罫線自体は、``table.setShowVerticalLines(false);``などで非表示にすることでレンダラー内の``JTextArea``は、連続しているように見せかけ、上部の``JLabel``は``Border``を設定することで区切りを表示しています。
* 解説 [#explanation]
文字列を配置した`JTextArea`を各カラムごとに`JViewport`で表示する領域を切り取ってセルに貼り付けています。さらに`JTable`のセルの縦罫線自体を`table.setShowVerticalLines(false)`などで非表示にしてレンダラー内の`JTextArea`は連続しているように見せかけ、代わりに上部の`JLabel`は`Border`を設定することで区切りを表示しています。

- メモ
-- 列の入れ替えには対応していない
-- ``0``行目だけカラムヘッダのサイズを変更すると、描画がおかしくなる?
--- ``0``行目ではなく、一番上に表示されている行の表示が乱れている
--- ``JTextArea#scrollRectToVisible(...)``ではなく、``JViewport#setViewPosition(Point)``を使用すると正常にリサイズできる
--- %%``0``行目だけ高さ1のダミー行を追加して回避(ソートなどで問題が残る)%%
-- %%``JTable``のクリック(セル選択?)などで表示が乱れる場合がある%%
--- %%``JTable#repaint(Rectangle)``をオーバーライドして常に全体を描画することで回避%%
- 列の入れ替えには対応していない
- `0`行目だけカラムヘッダのサイズを変更すると描画がおかしくなる?
-- `0`行目ではなく一番上に表示されている行の表示が乱れている
-- `JTextArea#scrollRectToVisible(...)`ではなく`JViewport#setViewPosition(Point)`を使用すると正常にリサイズ可能
-- %%`0`行目だけ高さ`1`の仮行を追加して回避(ソートなどで問題が残る)%%
- %%`JTable`のクリック(セル選択?)などで表示が乱れる場合がある%%
-- %%`JTable#repaint(Rectangle)`をオーバーライドして常に全体を描画することで回避%%
- `JScrollPane`内に`JTextArea`を配置せずに直接`JTextArea`から表示領域を切り取っても良さそう?

**参考リンク [#k0cadf9c]
* 参考リンク [#reference]
- [http://docs.huihoo.com/javaone/2007/desktop/TS-3548.pdf PDF: Extreme GUI Makeover 2007]
-- via: [http://stackoverflow.com/questions/16305023/jtable-complex-cell-renderer java - JTable : Complex Cell Renderer - Stack Overflow]
-- via: [https://stackoverflow.com/questions/16305023/jtable-complex-cell-renderer java - JTable : Complex Cell Renderer - Stack Overflow]
- [[JTableの罫線の有無とセルの内余白を変更>Swing/IntercellSpacing]]

**コメント [#dcdb5bd1]
- JTableをスクロールするとおかしくなる? -- [[aterai]] &new{2013-06-04 (火) 13:37:19};
-- ``0``行目ではなく、一番上に表示されている行の表示が原因かもしれない。 -- [[aterai]] &new{2013-06-04 (火) 13:44:14};
-- 移動の幅からみて、``TableCellRenderer``の``Border``が関連しているような気がするけど、よく分からない。 -- [[aterai]] &new{2013-06-04 (火) 15:08:18};
-- 一番上の行のみの症状なので、ヘッダレンダラーとか関係してるのかと調べてたけど、``JViewport#setViewPosition(Point)``を使って直接ジャンプ?すれは、正常にヘッダサイズを変更できるようだ。もしかしたら[http://docs.oracle.com/javase/jp/6/api/javax/swing/JViewport.html 次にビューポートにペイントが呼び出されたときに、クリッピング領域がビューポートサイズより小さい場合には、タイマーが開始され全体をペイントし直す]せいだった? -- [[aterai]] &new{2013-06-04 (火) 18:29:12};
* コメント [#comment]
#comment
- `JTable`をスクロールするとおかしくなる? -- &user(aterai); &new{2013-06-04 (火) 13:37:19};
-- `0`行目ではなく、一番上に表示されている行の表示が原因かもしれない。 -- &user(aterai); &new{2013-06-04 (火) 13:44:14};
-- 移動の幅からみて、`TableCellRenderer`の`Border`が関連しているような気がするけど、よく分からない。 -- &user(aterai); &new{2013-06-04 (火) 15:08:18};
-- 一番上の行のみの症状なので、ヘッダセルレンダラーとか関係してるのかと調べてたけど、`JViewport#setViewPosition(Point)`を使って直接ジャンプ?すれは、正常にヘッダサイズを変更できるようだ。もしかしたら[https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/JViewport.html 次にビューポートにペイントが呼び出されたときに、クリッピング領域がビューポートサイズより小さい場合には、タイマーが開始され全体をペイントし直す]せい? -- &user(aterai); &new{2013-06-04 (火) 18:29:12};

#comment