PyQt的PyQt/WidSide如何撤消项目?

2024-05-19 15:53:12 发布

您现在位置:Python中文网/ 问答频道 /正文

短版

如何实现对PySide/PyQt中QListWidgetItems所做编辑的撤消功能?在

来自Qt教程的提示?

下面为Qt用户(c++)编写的教程可能有答案,但我不是一个c++用户,所以有点迷失了:Using Undo/Redo with Item Views

加长版

我使用一个QListWidget来学习如何绕过PyQt的Undo Framework(在这个主题的an article的帮助下)。当我自己实现一个命令(比如从列表中删除一个项目)时,我可以使用undo/redo。在

我还想让小部件中的QListWidgetItems可编辑。这很简单:只需将ItemIsEditable标志添加到每个项目。问题是,如何将这样的编辑推送到undo堆栈,然后才能撤消/重做它们?在

下面是一个简单的工作示例,它显示了一个列表,允许您删除项,以及撤消/重做这样的删除操作。应用程序同时显示列表和撤消堆栈。需要做什么才能将编辑放到堆栈上?在

简单的工作示例

from PySide import QtGui, QtCore

class TodoList(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.initUI()
        self.show()

    def initUI(self):
        self.todoList = self.makeTodoList()
        self.undoStack = QtGui.QUndoStack(self)
        undoView = QtGui.QUndoView(self.undoStack)
        buttonLayout = self.buttonSetup()
        mainLayout = QtGui.QHBoxLayout(self)
        mainLayout.addWidget(undoView)
        mainLayout.addWidget(self.todoList)
        mainLayout.addLayout(buttonLayout)
        self.setLayout(mainLayout)
        self.makeConnections()

    def buttonSetup(self):
        #Make buttons 
        self.deleteButton = QtGui.QPushButton("Delete")
        self.undoButton = QtGui.QPushButton("Undo")
        self.redoButton = QtGui.QPushButton("Redo")
        self.quitButton = QtGui.QPushButton("Quit")
        #Lay them out
        buttonLayout = QtGui.QVBoxLayout()
        buttonLayout.addWidget(self.deleteButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.undoButton)
        buttonLayout.addWidget(self.redoButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.quitButton)
        return buttonLayout

    def makeConnections(self):
        self.deleteButton.clicked.connect(self.deleteItem)
        self.quitButton.clicked.connect(self.close)
        self.undoButton.clicked.connect(self.undoStack.undo)
        self.redoButton.clicked.connect(self.undoStack.redo)

    def deleteItem(self):
        rowSelected=self.todoList.currentRow()
        rowItem = self.todoList.item(rowSelected)
        if rowItem is None:
            return
        command = CommandDelete(self.todoList, rowItem, rowSelected,
                                "Delete item '{0}'".format(rowItem.text()))
        self.undoStack.push(command)

    def makeTodoList(self):
        todoList = QtGui.QListWidget()
        allTasks = ('Fix door', 'Make dinner', 'Read', 
                    'Program in PySide', 'Be nice to everyone')
        for task in allTasks:
            todoItem=QtGui.QListWidgetItem(task)
            todoList.addItem(todoItem)
            todoItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
        return todoList


class CommandDelete(QtGui.QUndoCommand):
    def __init__(self, listWidget, item, row, description):
        super(CommandDelete, self).__init__(description)
        self.listWidget = listWidget
        self.string = item.text()
        self.row = row

    def redo(self):
        self.listWidget.takeItem(self.row)

    def undo(self):
        addItem = QtGui.QListWidgetItem(self.string)
        addItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
        self.listWidget.insertItem(self.row, addItem)

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    myList=TodoList()
    sys.exit(app.exec_())

注:我在QtCentre贴了一张earlier version of this question。在


Tags: self编辑initdefqtrowtodolistqtgui
3条回答

你提到的那个教程真的没什么帮助。视图撤销重做实现的方法确实很多,我们只需要选择最简单的一种。如果处理小列表,最简单的方法是保存每次更改的所有数据,并在每次撤消或重做操作时从头恢复完整列表。在

如果仍然需要原子更改列表,可以使用QListWidget::itemChanged信号跟踪用户所做的编辑。这有两个问题:

  • 列表中的任何其他项更改也将触发此信号,因此您需要将更改项的任何代码包装到QObject::blockSignals调用中以阻止不需要的信号。在
  • 没有办法获取以前的文本,只能获取新的文本。解决方案是将所有列表数据保存到变量中,在更改时使用并更新它,或者在编辑之前保存编辑项的文本。QListWidget对其内部编辑器状态非常缄默,所以我决定使用QListWidget::currentItemChanged,假设用户无法找到一种方法来编辑一个项目,而不首先使其成为当前的。在

所以这是使其工作的更改(除了在两个地方添加ItemIsEditable标志):

def __init__(self):
    #...
    self.todoList.itemChanged.connect(self.itemChanged)
    self.todoList.currentItemChanged.connect(self.currentItemChanged)
    self.textBeforeEdit = ""

def itemChanged(self, item):
    command = CommandEdit(self.todoList, item, self.todoList.row(item),
        self.textBeforeEdit, 
        "Rename item '{0}' to '{1}'".format(self.textBeforeEdit, item.text()))
    self.undoStack.push(command)

def currentItemChanged(self, item):
    self.textBeforeEdit = item.text()

新的变革课程:

^{pr2}$

每次验证并接受项的新文本时,将其另存为列表项数据。准半伪码:

OnItemEdited(Item* item)
{
    int dataRole{ 32 }; //or greater (see ItemDataRole documentation)

    if (Validate(item->text()) {

        item->setData(dataRole, item->text());

    } else { //Restore previous value

        item->setText(item->data(dataRole).toString());
    }
}
对不起,如果它看起来太像C++了。在

我会这样做:

创建一个自定义的^{}并使用以下两个信号:

  • editorEvent
  • closeEditor

editorEvent:保存当前状态

On closeEditor:获取新状态并创建一个QUndoCommand,它为Redo设置新状态,为Undo设置旧状态。在

相关问题 更多 >