You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:42:44