qt - 如何在 PySide/PyQt 中撤消 QStandardItem 的编辑?

标签 qt pyqt pyqt4 pyside

使用identical question asked about QListWidgets作为指导,我试图制作一个QStandardItemModel,在其中我可以撤消对项目的编辑。

正如下面的 SSCCE 中所示,我几乎完全复制了该示例,但进行了一些细微的调整,因为 currentItemChanged 不适用于 QStandardItemModel。为了解决这个问题,我使用 clicked 信号来修复项目的先前文本。

奇怪的是,正确的描述显示在撤消堆栈中,但是当我单击撤消按钮时,它实际上并没有撤消任何内容。

请注意,当前问题表面上与 this question 相同。其他版本所接受的答案与其说是答案,不如说是暗示。这是我试图在这里实现的提示,但它还没有起作用。由于这个问题更加具体和详细,因此在我看来,它不应被视为重复。

SSCCE

from PySide import QtGui, QtCore
import sys

class CommandItemEdit(QtGui.QUndoCommand):
    def __init__(self, model, item, textBeforeEdit, description = "Item edited"):
        QtGui.QUndoCommand.__init__(self, description)
        self.model = model
        self.item = item
        self.textBeforeEdit = textBeforeEdit
        self.textAfterEdit = item.text()

    def redo(self):
        self.model.blockSignals(True)  
        self.item.setText(self.textAfterEdit)
        self.model.blockSignals(False)

    def undo(self):
        self.model.blockSignals(True)
        self.item.setText(self.textBeforeEdit)
        self.model.blockSignals(False)     


class UndoableTree(QtGui.QWidget):
    def __init__(self, parent = None):
        QtGui.QWidget.__init__(self, parent = None)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.view = QtGui.QTreeView()
        self.model = self.createModel()
        self.view.setModel(self.model)
        self.view.expandAll()
        self.undoStack = QtGui.QUndoStack(self)
        undoView = QtGui.QUndoView(self.undoStack)
        buttonLayout = self.buttonSetup()
        mainLayout = QtGui.QHBoxLayout(self)
        mainLayout.addWidget(undoView)
        mainLayout.addWidget(self.view)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)
        self.makeConnections()
        #For undo/redo editing
        self.textBeforeEdit = ""

    def makeConnections(self):
        self.view.clicked.connect(self.itemClicked)
        self.model.itemChanged.connect(self.itemChanged)
        self.quitButton.clicked.connect(self.close)
        self.undoButton.clicked.connect(self.undoStack.undo)
        self.redoButton.clicked.connect(self.undoStack.redo)

    def itemClicked(self, index):
        item = self.model.itemFromIndex(index)
        self.textBeforeEdit = item.text()  

    def itemChanged(self, item):
        command = CommandItemEdit(self.model, item, self.textBeforeEdit, 
            "Renamed '{0}' to '{1}'".format(self.textBeforeEdit, item.text()))
        self.undoStack.push(command)


    def buttonSetup(self):
        self.undoButton = QtGui.QPushButton("Undo")
        self.redoButton = QtGui.QPushButton("Redo")
        self.quitButton = QtGui.QPushButton("Quit")
        buttonLayout = QtGui.QVBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.undoButton)
        buttonLayout.addWidget(self.redoButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.quitButton)
        return buttonLayout

    def createModel(self):
        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(['Titles', 'Summaries'])
        rootItem = model.invisibleRootItem()
        #First top-level row and children 
        item0 = [QtGui.QStandardItem('Title0'), QtGui.QStandardItem('Summary0')]
        item00 = [QtGui.QStandardItem('Title00'), QtGui.QStandardItem('Summary00')]
        item01 = [QtGui.QStandardItem('Title01'), QtGui.QStandardItem('Summary01')]
        rootItem.appendRow(item0)
        item0[0].appendRow(item00)
        item0[0].appendRow(item01)
        #Second top-level item and its children
        item1 = [QtGui.QStandardItem('Title1'), QtGui.QStandardItem('Summary1')]
        item10 = [QtGui.QStandardItem('Title10'), QtGui.QStandardItem('Summary10')]
        item11 = [QtGui.QStandardItem('Title11'), QtGui.QStandardItem('Summary11')]
        rootItem.appendRow(item1)
        item1[0].appendRow(item10)
        item1[0].appendRow(item11)

        return model


def main():
    app = QtGui.QApplication(sys.argv)
    newTree = UndoableTree()
    newTree.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

最佳答案

出现此问题的原因是 blockSignals() 阻止 TreeView 被告知重新绘制。我认为这是因为当模型中的数据被修改时,模型会向 TreeView 发出信号,而当您调用 model.blockSignals(True) 时,该信号显然会被阻止。如果您在单击撤消/重做后手动调整窗口大小(显然只有在需要撤消/重做的情况下才有效),您会看到撤消/重做实际上已应用,只是最初没有显示它。

为了解决这个问题,我修改了代码,这样我们就可以断开相关信号并重新连接,而不是阻塞信号。这允许模型和 TreeView 在撤消/重做正在进行时继续正确通信。

看下面的代码

from PySide import QtGui, QtCore
import sys

class CommandItemEdit(QtGui.QUndoCommand):
    def __init__(self, connectSignals, disconnectSignals, model, item, textBeforeEdit, description = "Item edited"):
        QtGui.QUndoCommand.__init__(self, description)
        self.model = model
        self.item = item
        self.textBeforeEdit = textBeforeEdit
        self.textAfterEdit = item.text()
        self.connectSignals = connectSignals
        self.disconnectSignals = disconnectSignals

    def redo(self):
        self.disconnectSignals()
        self.item.setText(self.textAfterEdit)
        self.connectSignals()

    def undo(self):
        self.disconnectSignals()
        self.item.setText(self.textBeforeEdit)
        self.connectSignals()


class UndoableTree(QtGui.QWidget):
    def __init__(self, parent = None):
        QtGui.QWidget.__init__(self, parent = None)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.view = QtGui.QTreeView()
        self.model = self.createModel()
        self.view.setModel(self.model)
        self.view.expandAll()
        self.undoStack = QtGui.QUndoStack(self)
        undoView = QtGui.QUndoView(self.undoStack)
        buttonLayout = self.buttonSetup()
        mainLayout = QtGui.QHBoxLayout(self)
        mainLayout.addWidget(undoView)
        mainLayout.addWidget(self.view)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)
        self.makeConnections()
        #For undo/redo editing
        self.textBeforeEdit = ""

    def makeConnections(self):
        self.view.clicked.connect(self.itemClicked)
        self.model.itemChanged.connect(self.itemChanged)
        self.quitButton.clicked.connect(self.close)
        self.undoButton.clicked.connect(self.undoStack.undo)
        self.redoButton.clicked.connect(self.undoStack.redo)

    def disconnectSignal(self):    
        self.model.itemChanged.disconnect(self.itemChanged)

    def connectSignal(self):
        self.model.itemChanged.connect(self.itemChanged)

    def itemClicked(self, index):
        item = self.model.itemFromIndex(index)
        self.textBeforeEdit = item.text()  

    def itemChanged(self, item):
        command = CommandItemEdit(self.connectSignal, self.disconnectSignal, self.model, item, self.textBeforeEdit, 
            "Renamed '{0}' to '{1}'".format(self.textBeforeEdit, item.text()))
        self.undoStack.push(command)


    def buttonSetup(self):
        self.undoButton = QtGui.QPushButton("Undo")
        self.redoButton = QtGui.QPushButton("Redo")
        self.quitButton = QtGui.QPushButton("Quit")
        buttonLayout = QtGui.QVBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.undoButton)
        buttonLayout.addWidget(self.redoButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.quitButton)
        return buttonLayout

    def createModel(self):
        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(['Titles', 'Summaries'])
        rootItem = model.invisibleRootItem()
        #First top-level row and children 
        item0 = [QtGui.QStandardItem('Title0'), QtGui.QStandardItem('Summary0')]
        item00 = [QtGui.QStandardItem('Title00'), QtGui.QStandardItem('Summary00')]
        item01 = [QtGui.QStandardItem('Title01'), QtGui.QStandardItem('Summary01')]
        rootItem.appendRow(item0)
        item0[0].appendRow(item00)
        item0[0].appendRow(item01)
        #Second top-level item and its children
        item1 = [QtGui.QStandardItem('Title1'), QtGui.QStandardItem('Summary1')]
        item10 = [QtGui.QStandardItem('Title10'), QtGui.QStandardItem('Summary10')]
        item11 = [QtGui.QStandardItem('Title11'), QtGui.QStandardItem('Summary11')]
        rootItem.appendRow(item1)
        item1[0].appendRow(item10)
        item1[0].appendRow(item11)

        return model


def main():
    app = QtGui.QApplication(sys.argv)
    newTree = UndoableTree()
    newTree.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

其他信息

我发现,如果您在解除信号阻塞后显式调用 self.model.layoutChanged.emit(),则可以使用 CommandItemEdit 的原始实现。这会强制 TreeView 更新,而不会导致调用 UndoableTree.itemChanged() 插槽。

请注意, TreeView 连接到模型信号,而 TreeView 又连接到 UndoableTree.itemChanged() 插槽。

我还尝试发出 dataChanged() 信号,但这最终会调用仍然连接的 UndoableTree.itemChanged() 插槽,从而导致无限递归。我认为这是信号是调用 model.blockSignals() 的目标,因此不显式调用它是有意义的!

所以最后,虽然这些附加方法之一确实有效,但我仍然会采用我的第一个答案,即显式断开信号。这只是因为我认为最好让模型和 TreeView 之间的通信保持完整,而不是在手动触发您仍然想要的信号时限制某些通信。后一种途径可能会产生意想不到的副作用,并且调试起来很痛苦。

关于qt - 如何在 PySide/PyQt 中撤消 QStandardItem 的编辑?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29527610/

相关文章:

Qt QML 嵌套 ListView ,奇怪的滚动行为

c++ - Qt,无法显示子部件

python-2.7 - 在 PyQt 中从外部 GUI 类访问 GUI 元素

python - 在 Python 中使用 QListWidgetItem 中单击的项目调用函数

python - 删除所选项目上的虚线矩形

c++ - Qt GUI 变得 react 迟钝,发出信号的速度太快

qt - 缺少文件 [Qt]

python - 用python在屏幕上用鼠标画画?

python - PyQT - 定位和显示自定义小部件

python - QTab 小部件。如何将一个标签移动到正确的位置?