• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JTableのセルにJButtonを追加して行削除
#navi(../)
RIGHT:Posted by [[terai]] at 2007-10-22
*JTableのセルにJButtonを追加して行削除 [#y5a20522]
JTableのセルにJButtonを追加し、クリックされたらその行を削除します。主に[[Swing - JButton inside JTable Cell>http://forums.sun.com/thread.jspa?threadID=680674]]の投稿を参考にしています。
---
category: swing
folder: DeleteButtonInCell
title: JTableのセルにJButtonを追加して行削除
tags: [JTable, JButton, TableCellRenderer, TableCellEditor, ActionListener]
author: aterai
pubdate: 2007-10-22T07:55:05+09:00
description: JTableのセルにJButtonを追加し、クリックされたらその行を削除します。
image: https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTKsRqqqeI/AAAAAAAAAWY/X0y-Ph7jngA/s800/DeleteButtonInCell.png
---
* 概要 [#summary]
`JTable`のセルに`JButton`を追加し、クリックされたらその行を削除します。

-&jnlp;
-&jar;
-&zip;
#download(https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTKsRqqqeI/AAAAAAAAAWY/X0y-Ph7jngA/s800/DeleteButtonInCell.png)

#screenshot
* サンプルコード [#sourcecode]
#code(link){{
class DeleteButton extends JButton {
  @Override public void updateUI() {
    super.updateUI();
    setBorder(BorderFactory.createEmptyBorder());
    setFocusable(false);
    setRolloverEnabled(false);
    setText("X");
  }
}

**サンプルコード [#i9dca28f]
#code{{
class ButtonColumn extends AbstractCellEditor
    implements TableCellRenderer, TableCellEditor {
  private static final String LABEL = "X";
  private final JButton renderButton = new JButton(LABEL);
  private final JButton editorButton;
  public ButtonColumn() {
class DeleteButtonRenderer extends DeleteButton implements TableCellRenderer {
  public DeleteButtonRenderer() {
    super();
    editorButton = new JButton(new AbstractAction(LABEL) {
      public void actionPerformed(ActionEvent e) {
    setName("Table.cellRenderer");
  }

  @Override public Component getTableCellRendererComponent(JTable table,
      Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    return this;
  }
}

class DeleteButtonEditor extends DeleteButton implements TableCellEditor {
  public DeleteButtonEditor(final JTable table) {
    super();
    addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        int row = table.convertRowIndexToModel(table.getEditingRow());
        fireEditingStopped();
        ((DefaultTableModel) table.getModel()).removeRow(row);
      }
    });
    editorButton.setBorder(BorderFactory.createEmptyBorder());
    renderButton.setBorder(BorderFactory.createEmptyBorder());
    editorButton.setFocusPainted(false);
    editorButton.setRolloverEnabled(false);
    //renderButton.setToolTipText("Delete(renderButton)");
    //editorButton.setToolTipText("Delete(editorButton)");
  }
  public Component getTableCellRendererComponent(JTable table, Object value,
                      boolean isSelected, boolean hasFocus, int row, int column) {
    return renderButton;

  @Override public Component getTableCellEditorComponent(JTable table,
      Object value, boolean isSelected, int row, int column) {
    return this;
  }
  public Component getTableCellEditorComponent(JTable table, Object value,
                      boolean isSelected, int row, int column) {
    return editorButton;

  @Override public Object getCellEditorValue() {
    return "";
  }
  public Object getCellEditorValue() {
    return LABEL;
  }

  // Copied from AbstractCellEditor
  // protected EventListenerList listenerList = new EventListenerList();
  // transient protected ChangeEvent changeEvent = null;
  // ...
}
}}

**解説 [#qcbd9c4d]
上記のサンプルでは、ボタンがクリックされたときの削除自体は、JTableに追加したマウスリスナーで行っており、セルエディタやセルレンダラーに使っているJButtonは表示のためのダミーです。
#code{{
table.addMouseListener(new MouseAdapter() {
  private int targetRow = -1;
  @Override public void mousePressed(MouseEvent e) {
    Point pt = e.getPoint();
    int mcol = table.convertColumnIndexToModel(table.columnAtPoint(pt));
    int vrow = table.rowAtPoint(e.getPoint());
    int mrow = (vrow>=0)?table.convertRowIndexToModel(vrow):-1;
    if(mrow>=0 && mcol==BUTTON_COLUMN) {
      targetRow = mrow;
    }
  }
  @Override public void mouseReleased(MouseEvent e) {
    Point pt = e.getPoint();
    int mcol = table.convertColumnIndexToModel(table.columnAtPoint(pt));
    int vrow = table.rowAtPoint(e.getPoint());
    int mrow = (vrow>=0)?table.convertRowIndexToModel(vrow):-1;
    if(targetRow==mrow && mcol==BUTTON_COLUMN) {
      model.removeRow(mrow);
    }
    targetRow = -1;
  }
});
}}
* 解説 [#explanation]
- セルレンダラーに使用する`JButton`は表示専用でクリックイベントなどは無視される
- セルエディタとして使用する`JButton`に`ActionListener`を追加し、この`JButton`がクリックされたら`AbstractCellEditor`からコピーした`fireEditingStopped()`メソッドでセルの編集を終了し、`TableModel`から対象行を削除
-- セルレンダラー、セルエディタがコンポーネント(もしくは`DefaultCellEditor`)を継承していないと`JTable`の`LookAndFeel`を変更てもセルレンダラー、セルエディタの`updateUI()`が呼ばれない
--- `JTable#updateUI()`、`Java 1.6.0`の`JTable#updateSubComponentUI(...)`、`Java 1.7.0`の`SwingUtilities#updateRendererOrEditorUI(Object)`を参照
-- `AbstractCellEditor`を継承していても`updateUI()`は呼ばれない、`DefaultCellEditor`は継承しづらい…

複数選択を許可しない場合は、[[Swing - JButton inside JTable Cell>http://forums.sun.com/thread.jspa?threadID=680674]]のように、セルエディタにリスナーを追加し、選択されたセルを削除する方法もあります。
#code{{
public ButtonColumn(final JTable table) {
  super();
  table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  editorButton = new JButton(new AbstractAction(LABEL) {
    public void actionPerformed(ActionEvent e) {
      fireEditingStopped();
      int row = table.convertRowIndexToModel(table.getSelectedRow());
      ((DefaultTableModel)table.getModel()).removeRow(row);
    }
  });
  //...
}}
複数選択を許可する場合は、Ctrlキーなどを押しながらのボタンクリックに注意する必要があります。
* 参考リンク [#reference]
- [https://community.oracle.com/thread/1357728 Swing - JButton inside JTable Cell]
- [[JTableの行を追加、削除>Swing/AddRow]]
- [[JTableの行を全削除>Swing/ClearTable]]
- [[JTableのセルに複数のJButtonを配置する>Swing/MultipleButtonsInTableCell]]
- [[JTableのセルにHyperlinkを表示>Swing/HyperlinkInTableCell]]

**参考リンク [#kcf3a9d4]
-[[Swing - JButton inside JTable Cell>http://forums.sun.com/thread.jspa?threadID=680674]]
-[[JTableの行を追加、削除>Swing/AddRow]]
-[[JTableの行を全削除>Swing/ClearTable]]
* コメント [#comment]
#comment
- ボタンのセル内でマウスを移動しても削除するように変更。 -- &user(aterai); &new{2008-03-28 (金) 16:59:11};
-- メモ: %%`0`行目のボタンをクリックし、真上のヘッダ上でリリースしても削除可能%% 修正済み: [https://bugs.openjdk.org/browse/JDK-6291631 Bug ID: 6291631 JTable: rowAtPoint returns 0 for negative y] (追記: このバグは未修正になっているけど、`JDK 1.6, 1.7`などのソースではコメントにある修正が追加されている) -- &user(aterai); &new{2008-03-28 (金) 17:21:10};

**コメント [#t2516d67]
- ボタンのセル内でマウスを移動しても削除するように変更。 -- [[terai]] &new{2008-03-28 (金) 16:59:11};
-- メモ: 0行目のボタンをクリックし、真上のヘッダ上でリリースしても削除できる -> [[Bug ID: 6291631 JTable: rowAtPoint returns 0 for negative y>http://bugs.sun.com/view_bug.do?bug_id=6291631]] -- [[terai]] &new{2008-03-28 (金) 17:21:10};
#code{{
//上記のBug Databaseにある回避方法
JTable table = new JTable(model) {
  @Override public int rowAtPoint(Point pt) {
    return (pt.y<0)?-1:super.rowAtPoint(pt);
    return (pt.y < 0) ? -1 : super.rowAtPoint(pt);
  }
};
}}
- テスト -- [[terai]] &new{2009-09-27 (日) 01:34:58};
#code{{
import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.table.*;
public class MultipleButtonsInCellTest2 {
  JTable t;
  public JComponent makeUI() {
    String[] columnNames = {"String", "Button"};
    Object[][] data = { {"AAA", null}, {"BBB", null} };
    DefaultTableModel model = new DefaultTableModel(data, columnNames) {
      @Override public Class<?> getColumnClass(int column) {
        return getValueAt(0, column).getClass();
      }
    };
    t = new JTable(model);
    t.addMouseListener(new MouseAdapter() {
      private boolean isOnButtonColumn(MouseEvent e) {
        Point pt = e.getPoint();
        int mcol = t.convertColumnIndexToModel(t.columnAtPoint(pt));
        int vrow = t.rowAtPoint(pt);
        int mrow = (vrow>=0)?t.convertRowIndexToModel(vrow):-1;
        return mrow>=0 && mcol==1;
      }
      private Component getCellComponentAt(Point p) {
        int row = t.rowAtPoint(p);
        int col = t.columnAtPoint(p);
        TableCellEditor ce = t.getCellEditor(row,col);
        Component c = ce.getTableCellEditorComponent(t,null,true,row,col);
        Point pt = SwingUtilities.convertPoint(t, p, c);
        return SwingUtilities.getDeepestComponentAt(c, pt.x, pt.y);
      }
      @Override public void mousePressed(MouseEvent e) {
        if(isOnButtonColumn(e)) {
          Point pt = e.getPoint();
          component = getCellComponentAt(pt);
          t.editCellAt(t.rowAtPoint(pt), t.columnAtPoint(pt));
        }
      }
      @Override public void mouseReleased(MouseEvent e) {
        if(isOnButtonColumn(e)) {
          Component c = getCellComponentAt(e.getPoint());
          if(c instanceof JButton && c==component) ((JButton)c).doClick();
          component = null;
        }
      }
      Component component = null;
    });
    t.setRowHeight(36);
    ActionPanelEditorRenderer er = new ActionPanelEditorRenderer();
    TableColumn column = t.getColumnModel().getColumn(1);
    column.setCellRenderer(er);
    column.setCellEditor(er);
    JPanel p = new JPanel(new BorderLayout());
    p.add(new JScrollPane(t));
    p.setPreferredSize(new Dimension(320, 200));
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() { createAndShowGUI(); }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new MultipleButtonsInCellTest2().makeUI());
    f.pack();
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}
class ActionPanelEditorRenderer extends AbstractCellEditor
                                implements TableCellRenderer,TableCellEditor{
  JPanel renderer = new JPanel();
  JPanel editor   = new JPanel();
  public ActionPanelEditorRenderer() {
    super();
    JButton viewButton2 = new JButton(new AbstractAction("view2") {;
      public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "Viewing");
      }
    });
    JButton editButton2 = new JButton(new AbstractAction("edit2") {;
      public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "Editing");
      }
    });
    renderer.setOpaque(true);
    renderer.add(new JButton("view1"));
    renderer.add(new JButton("edit1"));
    editor.setOpaque(true);
    editor.add(viewButton2);
    editor.add(editButton2);
  }
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    renderer.setBackground(isSelected?table.getSelectionBackground()
                                     :table.getBackground());
    return renderer;
  }
  @Override public Component getTableCellEditorComponent(
      JTable table, Object value, boolean isSelected, int row, int column) {
    editor.setBackground(table.getSelectionBackground());
    return editor;
  }
  @Override public Object getCellEditorValue() { return null; }
}
}}

- テスト -- &user(aterai); &new{2009-09-27 (日) 01:34:58};
-- [[JTableのセルに複数のJButtonを配置する>Swing/MultipleButtonsInTableCell]]に移動。 -- &user(aterai); &new{2009-10-05 (日) 01:34:58};

#comment