如何在 PyQt5 中为 QComboBox 实现撤消重做?

How can I implement undo-redo for a QComboBox in PyQt5?

关于 Whosebug 上 QUndoCommand 的基础知识有很多问题,但我找不到 PyQt5 中 QComboBox 的 undo/redo 的最小工作示例,所以我正在尝试创建一个。但是,我遇到了以下代码的分段错误。


问题:


目标:


重现错误(使用下面的代码)


import sys
from PyQt5 import QtWidgets, QtCore

class MyUndoCommand(QtWidgets.QUndoCommand):
    def __init__(self, combobox, ind0, ind1):
        super().__init__()
        self.combobox = combobox
        self.ind0     = ind0
        self.ind1     = ind1

    def redo(self):
        self.combobox.setCurrentIndex( self.ind1 )

    def undo(self):
        self.combobox.setCurrentIndex( self.ind0 )


class MyComboBox(QtWidgets.QComboBox):
    def __init__(self, *args):
        super().__init__(*args)
        self.addItems( ['a', 'b', 'c'] )
        self.ind0  = 0
        self.undostack = QtWidgets.QUndoStack()
        self.currentIndexChanged.connect( self.on_index_changed )
    
    def keyPressEvent(self, e):
        z         = e.key() == QtCore.Qt.Key_Z
        ctrl      = e.modifiers() == QtCore.Qt.ControlModifier
        ctrlshift = e.modifiers() == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier
        if ctrl and z:
            if self.undostack.canUndo():
                self.undostack.undo()
        if ctrlshift and z:
            if self.undostack.canRedo():
                self.undostack.redo()

    def on_index_changed(self, ind):
        cmd = MyUndoCommand(self, self.ind0, ind)
        self.undostack.push( cmd )
        self.ind0  = ind



if __name__ == '__main__':
    app = QtWidgets.QApplication( sys.argv )
    widget = MyComboBox()
    widget.show()
    sys.exit(app.exec_())

问题是您将 currentIndexChanged 信号连接到一个无论如何都会创建撤消命令的函数,并且由于 MyUndoCommand 更改当前索引,结果是你得到一个递归调用。

一个可能的解决方案是创建一个标志,每当索引更改时都会检查该标志,并且 不会 在索引更改被另一个 [=24] 触发时创建进一步的撤消命令=].

class MyComboBox(QtWidgets.QComboBox):
    undoActive = False
    def __init__(self, *args):
        super().__init__(*args)
        self.addItems(['a', 'b', 'c'])
        self.ind0 = 0
        self.undostack = QtWidgets.QUndoStack()
        self.currentIndexChanged.connect(self.on_index_changed)
    
    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Z:
            ctrl = e.modifiers() == QtCore.Qt.ControlModifier
            ctrlshift = e.modifiers() == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier
            if ctrl and self.undostack.canUndo():
                self.undoActive = True
                self.undostack.undo()
                self.undoActive = False
                return
            elif ctrlshift and self.undostack.canRedo():
                self.undoActive = True
                self.undostack.redo()
                self.undoActive = False
                return
        super().keyPressEvent(e)

    def on_index_changed(self, ind):
        if not self.undoActive:
            cmd = MyUndoCommand(self, self.ind0, ind)
            self.undostack.push( cmd )
        self.undoActive = False
        self.ind0 = ind

请注意,我更改了 keyPressEvent 处理程序以确保处理未处理的键事件,这对于键盘导航和项目选择很重要。