V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
levelworm
V2EX  ›  Python

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

  •  
  •   levelworm · 2020-05-19 02:52:21 +08:00 · 2365 次点击
    这是一个创建于 1668 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我目前使用的是 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来,求问有没有什么具体的范例?多谢!

    2 条回复    2020-05-19 21:35:54 +08:00
    islxyqwe
        1
    islxyqwe  
       2020-05-19 09:15:10 +08:00   ❤️ 1
    做成 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
        2
    levelworm  
    OP
       2020-05-19 21:35:54 +08:00
    @islxyqwe 多谢~~我又看了下文档,大致明白了,是个 Command Pattern 的意思。不过看起来蛮复杂的,我目前需求不多,决定还是用目前的办法,的确内存消耗大,不过每个快照也就几百 KB,我限制在 10 个快照基本上够用了。

    等改天有空了我再仔细研究下正统做法是什么~~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5514 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 01:57 · PVG 09:57 · LAX 17:57 · JFK 20:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.