为什么QUndoStack.push()执行QUndoCommand.redo()?

mus*_*nte 3 python qt pyqt pyqt4 pyqt5

我创建了一个自定义QPushButton,允许从菜单或a中选择颜色QColorDialog.由于它是"主题"编辑器的一部分,我还添加了对QUndoStack的支持:每次更改自定义按钮的颜色时,它都会创建一个QUndoCommand的子类,并将其推送到QUndoStack.
然后我意识到(根据这个)每次执行a时QUndoStack.push(cmd),该命令都会执行(显然会创建一个递归,由PyQt自动"忽略",但仍然在stdout中报告).

通过redo()调用时阻止目标小部件上的信号来解决问题,但问题仍然存在:为什么首先执行推送命令?

从我的角度来看,如果我将命令推送到撤消堆栈,它已经被执行了.
什么情况下(理论上已经)执行[子类] QUndoCommand必须"再次"执行?
这种情况是否常见,需要将此类实现作为默认行为?

接下来,一个最小的和不完整的(但足以显示问题)的例子; 它不支持撤消/重做动作的信号处理,但这不是重点(我认为?).据我所知,问题出现在调用QUndoCommand创建的信号与创建信号本身的插槽重合时:

#!/usr/bin/env python2

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class UndoCmd(QtWidgets.QUndoCommand):
    def __init__(self, widget, newColor, oldColor):
        QtWidgets.QUndoCommand.__init__(self)
        self.widget = widget
        self.newColor = newColor
        self.oldColor = oldColor

    def redo(self):
        self.widget.color = self.newColor


class ColorButton(QtWidgets.QPushButton):
    colorChanged = QtCore.pyqtSignal(QtGui.QColor)
    def __init__(self, parent=None):
        QtWidgets.QPushButton.__init__(self, 'colorButton', parent)
        self.oldColor = self._color = self.palette().color(self.palette().Button)
        self.clicked.connect(self.changeColor)

    @QtCore.pyqtProperty(QtGui.QColor)
    def color(self):
        return self._color

    @color.setter
    def color(self, color):
        self._color = color
        palette = self.palette()
        palette.setColor(palette.Button, color)
        self.setPalette(palette)
        self.colorChanged.emit(color)

    def changeColor(self):
        dialog = QtWidgets.QColorDialog()
        if dialog.exec_():
            self.color = dialog.selectedColor()

class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QHBoxLayout()
        self.setLayout(layout)
        self.colorButton = ColorButton()
        layout.addWidget(self.colorButton)
        self.undoStack = QtWidgets.QUndoStack()
        self.colorButton.colorChanged.connect(lambda: self.colorChanged(self.colorButton.oldColor))

    def colorChanged(self, oldColor):
        self.undoStack.push(UndoCmd(self.colorButton, oldColor, self.colorButton._color))


app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)

Mit*_*tch 6

概述文档中对此进行了描述:

Qt的Undo Framework是Command模式的一种实现,用于在应用程序中实现撤消/重做功能.

Command模式基于以下思想:应用程序中的所有编辑都是通过创建命令对象的实例来完成的.命令对象将更改应用于文档并存储在命令堆栈中.此外,每个命令都知道如何撤消其更改以使文档恢复到以前的状态.只要应用程序仅使用命令对象来更改文档的状态,就可以通过向下遍历堆栈并依次对每个命令调用undo来撤消一系列命令.也可以通过向上遍历堆栈并在每个命令上调用重做来重做一系列命令.

要使用撤消框架,您应确保任何应该可撤消的操作仅由QUndoCommand子类执行.

所以我假设您正在执行两次操作:一次直接执行,然后通过撤消框架执行操作.相反,您应该只通过撤消框架执行操作.

推送操作立即重做的原因可能是为了简单:redo()函数已经方便地执行了操作,那么为什么要添加另一个函数来执行相同的操作呢?


dte*_*ech 5

redo()是同义词do(),不要让“re”部分混淆你。API 有两个关键功能,一个应用命令,一个反转命令。

还有一个次优的措辞选择,因为undo stack实际上应该称为command stack.

因此,它是合乎逻辑的,一个命令是applied因为它是addedcommand stack。在此之前不应手动应用该命令。