• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JTableのCellにJCheckBoxを複数配置する
#navi(../)
#tags()
RIGHT:Posted by &author(aterai); at 2011-02-28
*JTableのCellにJCheckBoxを複数配置する [#v80be979]
JTableのセル中にJCheckBoxを複数個配置します。

-&jnlp;
-&jar;
-&zip;

//#screenshot
#ref(https://lh4.googleusercontent.com/_9Z4BYR88imo/TWs6JY73P8I/AAAAAAAAA2M/wwrwT7R5K4k/s800/CheckBoxesInTableCell.png)

**サンプルコード [#p4dd25d7]
#code{{
#code(link){{
class CheckBoxesPanel extends JPanel {
  protected final String[] title = {"r", "w", "x"};
  public JCheckBox[] buttons;
  public CheckBoxesPanel() {
    super();
    setOpaque(false);
    setBackground(new Color(0,0,0,0));
    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
    initButtons();
  }
  protected void initButtons() {
    buttons = new JCheckBox[title.length];
    for(int i=0; i<buttons.length; i++) {
      JCheckBox b = new JCheckBox(title[i]);
      b.setOpaque(false);
      b.setFocusable(false);
      b.setRolloverEnabled(false);
      b.setBackground(new Color(0,0,0,0));
      buttons[i] = b;
      add(b);
      add(Box.createHorizontalStrut(5));
    }
  }
  private static final String OSNAME = System.getProperty("os.name");
  protected void updateButtons(Object v) {
    if("Windows 7".equals(OSNAME)) { //Windows aero?
      removeAll();
      initButtons();
    }
    Integer i = (Integer)(v==null?0:v);
    buttons[0].setSelected((i&(1<<2))!=0);
    buttons[1].setSelected((i&(1<<1))!=0);
    buttons[2].setSelected((i&(1<<0))!=0);
  }
}
}}

#code{{
class CheckBoxesRenderer extends CheckBoxesPanel
                         implements TableCellRenderer, Serializable {
  public CheckBoxesRenderer() {
    super();
    setName("Table.cellRenderer");
  }
  @Override public Component getTableCellRendererComponent(JTable table,
      Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    updateButtons(value);
    return this;
  }
  public static class UIResource extends CheckBoxesRenderer
                                 implements UIResource{}
}
}}

#code{{
class CheckBoxesEditor extends CheckBoxesPanel
                       implements TableCellEditor, Serializable {
  public CheckBoxesEditor() {
    ActionListener al = new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        fireEditingStopped();
      }
    };
    ActionMap am = getActionMap();
    for(int i=0; i<buttons.length; i++) {
      final JCheckBox b = buttons[i];
      b.addActionListener(al);
      am.put(title[i], new AbstractAction(title[i]) {
        public void actionPerformed(ActionEvent e) {
          b.setSelected(!b.isSelected());
          fireEditingStopped();
        }
      });
    }
    InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), title[0]);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), title[1]);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), title[2]);
  }
  @Override public Component getTableCellEditorComponent(JTable table,
        Object value, boolean isSelected, int row, int column) {
    updateButtons(value);
    return this;
  }
  @Override public Object getCellEditorValue() {
    int i = 0;
    if(buttons[0].isSelected()) i|=1<<2;
    if(buttons[1].isSelected()) i|=1<<1;
    if(buttons[2].isSelected()) i|=1<<0;
    return i;
  }

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

**解説 [#mb2dc2f3]
上記のサンプルでは、JTableのCell内に3つのJCheckBoxを配置したJPanelを作成し、これをCellRendererとCellEditorとして別々に使用しています。JCheckBoxをマウスでクリックすると、そのJCheckBoxの選択状態だけが変化します。
//(注: キーボードからの入力には対応していません)。

----
ヘッダカラムの移動、リサイズ(JFrameなどのリサイズ)で、チェックした内容が消えてしまわないように、CellEditorのチェックボックスがクリックされたらfireEditingStopped()メソッドを呼び出して編集を終了し更新を確定するようにしています。

%%JTable自体に以下の様なMouseListenerを追加してチェックボックスがクリックされるたびにtable.getCellEditor(row, col).stopCellEditing();を呼び出しています。%%

//#code{{
//table.addMouseListener(new MouseAdapter() {
//  @Override public void mouseReleased(MouseEvent e) {
//    JTable t = (JTable)e.getComponent();
//    Point p  = e.getPoint();
//    int row  = t.rowAtPoint(p);
//    int col  = t.columnAtPoint(p);
//    if(t.convertColumnIndexToModel(col)==1) {
//      t.getCellEditor(row, col).stopCellEditing();
//    }
//  }
//});
//}}

//JTableにではなく、CellEditor自体にMouseListenerを追加する方法もあります。
//#code{{
//class CheckBoxEditorRenderer2 extends CheckBoxEditorRenderer
//                              implements MouseListener {
//  private final JTable table;
//  public CheckBoxEditorRenderer2(JTable table) {
//    super();
//    this.table = table;
//    editor.addMouseListener(this);
//  }
//  //Copied form http://tips4java.wordpress.com/2009/07/12/table-button-column/
//  private boolean isButtonColumnEditor;
//  @Override public void mousePressed(MouseEvent e) {
//    if(table.isEditing() &&  table.getCellEditor() == this) {
//      isButtonColumnEditor = true;
//    }
//  }
//  @Override public void mouseReleased(MouseEvent e) {
//    if(isButtonColumnEditor &&  table.isEditing()) {
//      table.getCellEditor().stopCellEditing();
//    }
//    isButtonColumnEditor = false;
//  }
//  @Override public void mouseClicked(MouseEvent e) {}
//  @Override public void mouseEntered(MouseEvent e) {}
//  @Override public void mouseExited(MouseEvent e) {}
//}
//}}

**参考リンク [#wc856fe8]
-[[JTableのセル中にJRadioButtonを配置>Swing/RadioButtonsInTableCell]]
-[[JTableのセルに複数のJButtonを配置する>Swing/MultipleButtonsInTableCell]]
-[[JCheckBoxのセルをロールオーバーする>Swing/RolloverBooleanRenderer]]

**コメント [#xf4aaa77]
- ビットフラグをEnumSetに変更するテスト -- [[aterai]] &new{2011-03-01 (火) 14:22:06};
#code{{
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class EnumSetTest {
  String[] columnNames = { "user", "rwx" };
  Object[][] data = {
    {"owner", EnumSet.of(Permissions.READ, Permissions.WRITE, Permissions.EXECUTE)},
    {"group", EnumSet.of(Permissions.READ)},
    {"other", EnumSet.noneOf(Permissions.class)}
  };
  DefaultTableModel model = new DefaultTableModel(data, columnNames) {
    @Override public Class<?> getColumnClass(int column) {
      return getValueAt(0, column).getClass();
    }
  };
  JTable table = new JTable(model);
  public JComponent makeUI() {
    TableColumn c = table.getColumnModel().getColumn(1);
    c.setCellRenderer(new CheckBoxesRenderer());
    c.setCellEditor(new CheckBoxesEditor());
    c.setPreferredWidth(180);

    final EnumMap<Permissions, Integer> map =
      new EnumMap<Permissions, Integer>(Permissions.class);
    map.put(Permissions.READ,    1<<2);
    map.put(Permissions.WRITE,   1<<1);
    map.put(Permissions.EXECUTE, 1<<0);

    JPanel p = new JPanel(new BorderLayout());
    p.add(new JScrollPane(table));
    p.add(new JButton(new AbstractAction("chmod") {
      @Override public void actionPerformed(ActionEvent e) {
        StringBuilder buf = new StringBuilder(9);
        String M = "-";
        for (int i=0; i<model.getRowCount(); i++) {
          @SuppressWarnings("unchecked")
          EnumSet<Permissions> v = (EnumSet<Permissions>)model.getValueAt(i, 1);
          int flg = 0;
          if (v.contains(Permissions.READ)) {
            flg|=map.get(Permissions.READ);
            buf.append("r");
          } else {
            buf.append(M);
          }
          if (v.contains(Permissions.WRITE)) {
            flg|=map.get(Permissions.WRITE);
            buf.append("w");
          } else {
            buf.append(M);
          }
          if (v.contains(Permissions.EXECUTE)) {
            flg|=map.get(Permissions.EXECUTE);
            buf.append("x");
          } else {
            buf.append(M);
          }
          System.out.print(flg);
        }
        System.out.println(" "+M+buf.toString());
      }
    }), BorderLayout.SOUTH);
    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 EnumSetTest().makeUI());
    f.setSize(320,200);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}
enum Permissions { EXECUTE, WRITE, READ; }
class CheckBoxesPanel extends JPanel {
  protected final String[] title = {"r", "w", "x"};
  public final JCheckBox[] buttons;
  public CheckBoxesPanel() {
    super();
    setOpaque(false);
    setBackground(new Color(0,0,0,0));
    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
    buttons = new JCheckBox[title.length];
    for (int i=0; i<buttons.length; i++) {
      JCheckBox b = new JCheckBox(title[i]);
      b.setOpaque(false);
      b.setFocusable(false);
      b.setRolloverEnabled(false);
      b.setBackground(new Color(0,0,0,0));
      buttons[i] = b;
      add(b);
      add(Box.createHorizontalStrut(5));
    }
  }
  protected void updateButtons(Object v) {
    @SuppressWarnings("unchecked")
    EnumSet<Permissions> f = (v==null)? EnumSet.noneOf(Permissions.class)
                             :(EnumSet<Permissions>)v;
    buttons[0].setSelected(f.contains(Permissions.READ));
    buttons[1].setSelected(f.contains(Permissions.WRITE));
    buttons[2].setSelected(f.contains(Permissions.EXECUTE));
  }
//   protected void updateButtons(Object v) {
//     Integer i = (Integer)(v==null?0:v);
//     buttons[0].setSelected((i&(1<<2))!=0);
//     buttons[1].setSelected((i&(1<<1))!=0);
//     buttons[2].setSelected((i&(1<<0))!=0);
//   }
}

class CheckBoxesRenderer extends CheckBoxesPanel implements TableCellRenderer {
  public CheckBoxesRenderer() {
    super();
    setName("Table.cellRenderer");
  }
  @Override public Component getTableCellRendererComponent(JTable table,
      Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    updateButtons(value);
    return this;
  }
}

class CheckBoxesEditor extends CheckBoxesPanel implements TableCellEditor {
  @Override public Component getTableCellEditorComponent(JTable table,
      Object value, boolean isSelected, int row, int column) {
    updateButtons(value);
    return this;
  }
  @Override public Object getCellEditorValue() {
    EnumSet<Permissions> f = EnumSet.noneOf(Permissions.class);
    if (buttons[0].isSelected()) f.add(Permissions.READ);
    if (buttons[1].isSelected()) f.add(Permissions.WRITE);
    if (buttons[2].isSelected()) f.add(Permissions.EXECUTE);
    return f;
  }
//   @Override public Object getCellEditorValue() {
//     int i = 0;
//     if (buttons[0].isSelected()) i|=1<<2;
//     if (buttons[1].isSelected()) i|=1<<1;
//     if (buttons[2].isSelected()) i|=1<<0;
//     return i;
//   }

  //Copid from AbstractCellEditor
  //protected EventListenerList listenerList = new EventListenerList();
  transient protected ChangeEvent changeEvent = null;

  @Override public boolean isCellEditable(java.util.EventObject e) {
    return true;
  }
  @Override public boolean shouldSelectCell(java.util.EventObject anEvent) {
    return true;
  }
  @Override public boolean stopCellEditing() {
    fireEditingStopped();
    return true;
  }
  @Override public void  cancelCellEditing() {
    fireEditingCanceled();
  }
  @Override public void addCellEditorListener(CellEditorListener l) {
    listenerList.add(CellEditorListener.class, l);
  }
  @Override public void removeCellEditorListener(CellEditorListener l) {
    listenerList.remove(CellEditorListener.class, l);
  }
  public CellEditorListener[] getCellEditorListeners() {
    return (CellEditorListener[])listenerList.getListeners(CellEditorListener.class);
  }
  protected void fireEditingStopped() {
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==CellEditorListener.class) {
        if (changeEvent == null) changeEvent = new ChangeEvent(this);
        ((CellEditorListener)listeners[i+1]).editingStopped(changeEvent);
      }
    }
  }
  protected void fireEditingCanceled() {
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i]==CellEditorListener.class) {
        if (changeEvent == null) changeEvent = new ChangeEvent(this);
        ((CellEditorListener)listeners[i+1]).editingCanceled(changeEvent);
      }
    }
  }
}
}}
-- [http://www.ne.jp/asahi/hishidama/home/tech/java/enum.html#h2_flag フラグとしての論理和(EnumSetの例) - Java列挙型メモ(Hishidama's Java enum Memo)] -- [[aterai]] &new{2011-03-01 (火) 21:12:24};
-- [[JTableの列にEnumSetを使用する>Swing/EnumSet]]に移動
- rwxセル選択中にキーボードからr,w,xを入力するとチェックが切り替わるようにInputMap, ActionMapを追加。 -- [[aterai]] &new{2011-03-09 (水) 22:33:39};
- Windows環境でAero効果を有効にしていると?、残像が表示される場合がある?のを修正。 -- [[aterai]] &new{2011-11-01 (火) 18:12:50};

#comment