---
category: swing
folder: UndoableEditTable
title: JTableのセル編集をUndo可能にする
title-en: Making JTable cell edits undoable
tags: [JTable, UndoManager, UndoableEdit]
author: aterai
pubdate: 2025-12-15T00:58:29+09:00
description: JTableのCellEditorで実行したセル値の編集をキー入力でUndo、Redo可能に設定します。
summary-jp: JTableのCellEditorで実行したセル値の編集をキー入力でUndo、Redo可能に設定します。
summary-en: Enables undo and redo of cell value edits performed in a JTable's CellEditor via keyboard input.
image: https://drive.google.com/uc?id=1W7lnyGT5xYgJw3RT7SEFfPbguGLiB-Ny
---
* Summary [#summary]
`JTable`の`CellEditor`で実行したセル値の編集をキー入力で`Undo`、`Redo`可能に設定します。
// #en{{Enables undo and redo of cell value edits performed in a `JTable`'s `CellEditor` via keyboard input.}}
#download(https://drive.google.com/uc?id=1W7lnyGT5xYgJw3RT7SEFfPbguGLiB-Ny)
* Source Code Examples [#sourcecode]
#code(link){{
class PropertyTable extends JTable {
private static final int TARGET_COLUMN = 1;
private Class<?> editingClass;
private final UndoManager undoManager = new UndoManager();
protected PropertyTable(TableModel model) {
super(model);
initUndoActions();
}
private void initUndoActions() {
String undo = "undo";
String redo = "redo";
ActionMap am = getActionMap();
am.put(undo, new UndoAction());
am.put(redo, new RedoAction());
InputMap im = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
im.put(KeyStroke.getKeyStroke("ctrl Z"), undo);
im.put(KeyStroke.getKeyStroke("ctrl shift Z"), redo);
im.put(KeyStroke.getKeyStroke("ctrl Y"), redo);
}
@Override public void setValueAt(Object newValue, int row, int column) {
if (isEditing()) {
int mr = convertRowIndexToModel(row);
int mc = convertColumnIndexToModel(column);
Object oldValue = getValueAt(mr, mc);
if (!Objects.equals(oldValue, newValue)) {
TableModel m = getModel();
undoManager.addEdit(new UndoableCellEdit(m, mr, mc, oldValue, newValue));
}
}
super.setValueAt(newValue, row, column);
}
private class UndoAction extends AbstractAction {
private UndoAction() {
super("undo");
}
@Override public void actionPerformed(ActionEvent e) {
try {
undoManager.undo();
} catch (CannotUndoException ex) {
UIManager.getLookAndFeel().provideErrorFeedback(
(Component) e.getSource());
}
}
}
private class RedoAction extends AbstractAction {
private RedoAction() {
super("redo");
}
@Override public void actionPerformed(ActionEvent e) {
try {
undoManager.redo();
} catch (CannotRedoException ex) {
UIManager.getLookAndFeel().provideErrorFeedback(
(Component) e.getSource());
}
}
}
// ...
}
}}
* Description [#description]
- `JTable#setValueAt(...)`をオーバーライドしてセル値が現在のセル値と異なる場合は以下のような`AbstractUndoableEdit`を実装する`UndoableCellEdit`を作成し、`UndoManager#addEdit(...)`で追加
- `UndoManager#undo()`を実行するアクションを`JTable`の`ActionMap`に追加し、KBD{Ctrl+Z}キー入力でその`Undo`アクションを実行するよう`InputMap`に追加
- `UndoManager#redo()`を実行するアクションも同様に`JTable`の`ActionMap`に追加し、KBD{Ctrl+Y}、またはKBD{Ctrl+Shift+Z}キー入力でその`Redo`アクションを実行するよう`InputMap`に追加
-- `UndoManager#undo()`を実行するアクションを`JTable`の`ActionMap`に追加し、KBD{Ctrl+Z}キー入力でその`Undo`アクションを実行するよう`InputMap`に追加
-- `UndoManager#redo()`を実行するアクションも同様に`JTable`の`ActionMap`に追加し、KBD{Ctrl+Y}、またはKBD{Ctrl+Shift+Z}キー入力でその`Redo`アクションを実行するよう`InputMap`に追加
#code{{
class UndoableCellEdit extends AbstractUndoableEdit {
private final TableModel model;
private final int row;
private final int column;
private final Object oldValue;
private final Object newValue;
protected UndoableCellEdit(
TableModel m, int row, int col, Object oldVal, Object newVal) {
super();
this.model = m;
this.row = row;
this.column = col;
this.oldValue = oldVal;
this.newValue = newVal;
}
@Override public void undo() {
super.undo();
model.setValueAt(oldValue, row, column);
}
@Override public void redo() {
super.redo();
model.setValueAt(newValue, row, column);
}
}
}}
* Reference [#reference]
- [[JTableのセルエディタにJPopupMenuを設定>Swing/CellEditorPopupMenu]]
* Comment [#comment]
#comment
#comment