如何在 PySide/PyQt 中撤销对 QListWidgetItem 的编辑? [英] How to undo an edit of a QListWidgetItem in PySide/PyQt?

查看:53
本文介绍了如何在 PySide/PyQt 中撤销对 QListWidgetItem 的编辑?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

短版

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

How do you implement undo functionality for edits made on QListWidgetItems in PySide/PyQt?

来自 Qt 教程的提示?

以下为 Qt 用户 (c++) 编写的教程可能有答案,但我不是 c++ 人,所以有点迷路:对项目视图使用撤消/重做

The following tutorial written for Qt users (c++) likely has the answer, but I am not a c++ person, so get a bit lost: Using Undo/Redo with Item Views

更长的版本

我正在使用 QListWidget 来了解 PyQt 的 撤消框架(借助一篇文章题).当我自己实现一个命令(比如从列表中删除一个项目)时,我可以撤消/重做.

I am using a QListWidget to learn my way around PyQt's Undo Framework (with the help of an article on the topic). I am fine with undo/redo when I implement a command myself (like deleting an item from the list).

我还想让小部件中的 QListWidgetItems 可编辑.这很简单:只需将 ItemIsEditable 标志添加到每个项目.问题是,如何将此类编辑推送到撤消堆栈,以便我可以撤消/重做它们?

I also want to make the QListWidgetItems in the widget editable. This is easy enough: just add the ItemIsEditable flag to each item. The problem is, how can I push such edits onto the undo stack, so I can then undo/redo them?

下面是一个简单的工作示例,它显示了一个列表,可让您删除项目,以及撤消/重做此类删除.应用程序同时显示列表和撤消堆栈.需要做什么才能在该堆栈上进行编辑?

Below is a simple working example that shows a list, lets you delete items,and undo/redo such deletions. The application displays both the list and the the undo stack. What needs to be done to get edits onto that stack?

简单的工作示例

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_())

注意我发布了一个电子这个问题的早期版本在 Qt 中心.

Note I posted an earlier version of this question at QtCentre.

推荐答案

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

That tutorial you mentioned is really not very helpful. There are indeed many approaches to undo-redo implementation for views, we just need to choose the simplest one. If you deal with small lists, the simpliest way is to save all data on each change and restore full list from scratch on each undo or redo operation.

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

If you still want atomic changes list, you can track user-made edits with QListWidget::itemChanged signal. There are two problems with that:

  • 列表中的任何其他项目更改也会触发此信号,因此您需要将更改项目的任何代码包装到 QObject::blockSignals 调用中以阻止不需要的信号.
  • 无法获取以前的文本,只能获取新文本.解决方案是将所有列表数据保存到变量,在更改时使用和更新它,或者在编辑之前保存编辑项目的文本.QListWidget 对其内部编辑器状态非常谨慎,因此我决定使用 QListWidget::currentItemChanged 假设用户不会找到一种方法来编辑项目而不首先制作当前.
  • Any other item change in the list will also trigger this signal, so you need to wrap any code that changes items into QObject::blockSignals calls to block unwanted signals.
  • There is no way to get previous text, you can only get new text. The solution is either save all list data to variable, use and update it on change or save the edited item's text before it's edited. QListWidget is pretty reticent about its internal editor state, so I decided to use QListWidget::currentItemChanged assuming that user won't find a way to edit an item without making is current first.

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

So this is the changes that will make it work (besides adding ItemIsEditable flag in two places):

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()

以及新的更改类:

class CommandEdit(QtGui.QUndoCommand):
    def __init__(self, listWidget, item, row, textBeforeEdit, description):
        super(CommandEdit, self).__init__(description)
        self.listWidget = listWidget
        self.textBeforeEdit = textBeforeEdit
        self.textAfterEdit = item.text()
        self.row = row

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

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

这篇关于如何在 PySide/PyQt 中撤销对 QListWidgetItem 的编辑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆