如何在 PyQt5 中为 QComboBox 实现撤消重做?
How can I implement undo-redo for a QComboBox in PyQt5?
关于 Whosebug 上 QUndoCommand
的基础知识有很多问题,但我找不到 PyQt5 中 QComboBox
的 undo/redo 的最小工作示例,所以我正在尝试创建一个。但是,我遇到了以下代码的分段错误。
问题:
- 这段代码有什么问题?
- 创建最小工作示例需要更改什么?
目标:
- 键盘撤消:
Ctrl
+ Z
- 键盘重做:
Ctrl
+ Shift
+ Z
重现错误(使用下面的代码)
- 启动应用程序
- 将组合框选择从“a”更改为“b”
- 按
Ctrl
+ Z
撤消(这会正确地恢复为“a”)
- 按
Ctrl
+ Shift
+ Z
重做(这会产生分段错误)
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
处理程序以确保处理未处理的键事件,这对于键盘导航和项目选择很重要。
关于 Whosebug 上 QUndoCommand
的基础知识有很多问题,但我找不到 PyQt5 中 QComboBox
的 undo/redo 的最小工作示例,所以我正在尝试创建一个。但是,我遇到了以下代码的分段错误。
问题:
- 这段代码有什么问题?
- 创建最小工作示例需要更改什么?
目标:
- 键盘撤消:
Ctrl
+Z
- 键盘重做:
Ctrl
+Shift
+Z
重现错误(使用下面的代码)
- 启动应用程序
- 将组合框选择从“a”更改为“b”
- 按
Ctrl
+Z
撤消(这会正确地恢复为“a”) - 按
Ctrl
+Shift
+Z
重做(这会产生分段错误)
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
处理程序以确保处理未处理的键事件,这对于键盘导航和项目选择很重要。