PyQt5 QTableWidget单元格文本变更时获取前后内容实现撤销功能
实现QTableWidget单元格修改的撤销功能
核心思路
要实现撤销功能,核心是精准记录单元格修改前后的文本状态,通过维护一个撤销栈存储每一次修改的关键信息(行、列、旧文本、新文本),撤销时从栈中取出最近的修改记录,恢复单元格的旧文本即可。
具体实现方案
方案1:重写QTableWidget的setItem方法
所有修改单元格内容的操作(包括用户手动编辑、代码批量修改)最终都会调用setItem方法,重写该方法可以直接捕获修改前后的文本值:
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem class UndoableTableWidget(QTableWidget): def __init__(self, parent=None): super().__init__(parent) self.undo_stack = [] # 存储撤销记录:(行号, 列号, 旧文本, 新文本) self.redo_stack = [] # 可选:用于支持重做功能 def setItem(self, row, col, item): # 获取修改前的旧文本 old_item = self.item(row, col) old_text = old_item.text() if old_item else "" new_text = item.text() # 仅当新旧文本不同时记录操作,避免无效记录 if old_text != new_text: self.undo_stack.append((row, col, old_text, new_text)) self.redo_stack.clear() # 新操作会清空重做栈 # 调用父类方法完成单元格内容设置 super().setItem(row, col, item) def undo(self): if not self.undo_stack: return # 取出最近一次修改记录 row, col, old_text, _ = self.undo_stack.pop() # 保存当前状态到重做栈(如果需要重做) current_item = self.item(row, col) current_text = current_item.text() if current_item else "" self.redo_stack.append((row, col, current_text, old_text)) # 恢复旧文本 self.setItem(row, col, QTableWidgetItem(old_text)) def redo(self): if not self.redo_stack: return row, col, _, new_text = self.redo_stack.pop() current_item = self.item(row, col) current_text = current_item.text() if current_item else "" self.undo_stack.append((row, col, current_text, new_text)) # 恢复新文本 self.setItem(row, col, QTableWidgetItem(new_text))
方案2:通过事件过滤器捕获编辑前后的值
如果不想重写QTableWidget,可以通过给单元格编辑器安装事件过滤器,在编辑开始时记录旧文本,编辑结束时对比新文本并记录操作:
from PyQt5.QtWidgets import QTableWidget, QLineEdit from PyQt5.QtCore import QEvent class UndoTableWidget(QTableWidget): def __init__(self, parent=None): super().__init__(parent) self.undo_stack = [] self._edit_cache = {"row": -1, "col": -1, "old_text": ""} # 监听单元格双击编辑事件 self.cellDoubleClicked.connect(self._on_edit_start) def _on_edit_start(self, row, col): # 记录编辑前的文本和位置 item = self.item(row, col) self._edit_cache["old_text"] = item.text() if item else "" self._edit_cache["row"] = row self._edit_cache["col"] = col # 获取编辑器并安装事件过滤器 editor = self.itemDelegate().createEditor(self, item, row, col) if isinstance(editor, QLineEdit): editor.installEventFilter(self) def eventFilter(self, obj, event): # 捕获编辑器失去焦点(编辑结束)的事件 if isinstance(obj, QLineEdit) and event.type() == QEvent.FocusOut: row, col = self._edit_cache["row"], self._edit_cache["col"] new_text = obj.text() old_text = self._edit_cache["old_text"] # 记录有效修改 if old_text != new_text: self.undo_stack.append((row, col, old_text, new_text)) # 重置缓存 self._edit_cache = {"row": -1, "col": -1, "old_text": ""} return super().eventFilter(obj, event) def undo(self): if not self.undo_stack: return row, col, old_text, _ = self.undo_stack.pop() self.setItem(row, col, QTableWidgetItem(old_text))
注意事项
- 两种方案都需要判断新旧文本是否不同,避免记录无意义的操作(比如用户编辑后又改回原内容)。
- 若需要支持重做功能,需维护
redo_stack,每次撤销时将当前状态存入重做栈,重做时恢复对应状态。 - 针对批量修改(如粘贴多行内容),可以将连续的同类型修改合并为一条撤销记录,提升操作体验。
内容的提问来源于stack exchange,提问作者hate_code_47
相关产品推荐
相关产品推荐

