请问 PyQT5 中,如何处理 undo 和 redo?

2020-05-19 02:52:21 +08:00
 levelworm

我目前使用的是 subclass 的QTableViewQAbstractTableModel.

在 Stackoverflow 上搜索了一阵子,看起来正统的做法之一是在 Model 中包含一个QUndoStack,然后另外写一个 subclass QUndoCommand的类。但是我没有搞明白,到底在哪里调用最后这个类,所以也就没写出来。

我目前用的是笨办法。因为表格中的数据很小,大概就 500KB 左右。每次进行修改,就把上一次的数据直接深拷贝。同时在 View 里头截获 keypress,如果是Ctrl+Z就调用 Model 中的undo(),直接把深拷贝的原样直接覆盖到现有的数据上。简化后的代码如下:

# Model
class SlotConfigModel(QAbstractTableModel):
    def __init__(self, data):
        super(SlotConfigModel, self).__init__()
        self._data = data
        self._olddata: list = []
        self._lable = ("id", "Probability", "Reel 1", "Reel 2", "Reel 3")

    def setData(self, index, value, role=None):
        if role == Qt.EditRole:
        	# 每次编辑都把未修改前的 data 存档(需要深复制因为 data 是 list),undo 的时候覆盖。
            self._olddata.append(copy.deepcopy(self._data))
            print("Old data saved to list")
            row = index.row()
            col = index.column()
            self._data[index.row()][index.column()] = value
            return True
        return False

    def undo(self):
        if len(self._olddata) > 0:
            self._data = copy.deepcopy(self._olddata[-1])
            print("Undo successful!")
            self._olddata.pop()
        else:
            print("Nothing to Undo!")

# View
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.tableview = QTableView()
        self.horizontal_header = self.tableview.horizontalHeader()
        self.horizontal_header.setStretchLastSection(True)

        data = [
            [15, 0.13, "[101]", "[101]", "[101]"],
            [15, 0.03, "[101]", "[101]", "[102, 104, 5, 2, 1]"],
            [15, 0.04, "[101]", "[102;104;5;2;1]", "[101;102;104;5;2;1]"],
            [16, 0.20, "[5]", "[5]", "[5]"],
            [16, 0.50, "[101]", "[5]", "[102, 104, 5, 2, 1]"],
            [16, 0.10, "[5]", "[102;104;5;2;1]", "[101;102]"]
        ]

        self.model = SlotConfigModel(data)
        self.tableview.setModel(self.model)

        self.setCentralWidget(self.tableview)

    def keyPressEvent(self, event):
        if event.key() == (Qt.Key_Control and Qt.Key_Z):
        	# 如果 Ctrl-Z 则调用 Model 中的 undo()
            self.model.undo()
            # 手动更新 View,否则直到下一次交互才更新
            self.tableview.viewport().update()
        QTableView.keyPressEvent(self.tableview, event)

我觉得这个不是长久之计,比如说我还要写Insertrow()等方法,这样弄起来还是颇为费劲,最好是能用QUndoCommand来,求问有没有什么具体的范例?多谢!

2218 次点击
所在节点    Python
2 条回复
islxyqwe
2020-05-19 09:15:10 +08:00
做成 ES 式,把操作抽象成 args=>state Pre=>state Next
然后每个操作内容都存到队列,undo 了从头执行一遍。也可以每隔几个设置快照,从最近的快照开始执行历史操作。
还可以限制队列的长度,到达最大长度则每次也把起始状态更新,这样内存消耗固定,但只能 undo 几次。
你这个代码就相当于每次操作都有快照,内存消耗会比较大。

QUndoStack 的话,看起来是每个操作都要实现 undo 和 redo,redo 是正向的 state Pre=>state Next,还要对每种操作额外实现 undo 的 state Next => state Pre,然后 push 执行 redo,pop 执行 undo
levelworm
2020-05-19 21:35:54 +08:00
@islxyqwe 多谢~~我又看了下文档,大致明白了,是个 Command Pattern 的意思。不过看起来蛮复杂的,我目前需求不多,决定还是用目前的办法,的确内存消耗大,不过每个快照也就几百 KB,我限制在 10 个快照基本上够用了。

等改天有空了我再仔细研究下正统做法是什么~~

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/673073

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX