How to undo an edit of a QListWidgetItem in PySide/PyQt?

后端 未结 3 1206
深忆病人
深忆病人 2020-12-12 02:06

Short version

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

Hint from a Qt

相关标签:
3条回答
  • 2020-12-12 02:50

    I would do it like this:

    Create a custom QItemDelegate and use these two signals:

    • editorEvent
    • closeEditor

    On editorEvent: Save current state

    On closeEditor: Get new state and create a QUndoCommand that set the new state for Redo and the old state for Undo.

    0 讨论(0)
  • 2020-12-12 02:52

    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.

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

    • 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.

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

    And the new change class:

    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)
    
    0 讨论(0)
  • 2020-12-12 03:07

    Each time you verify and accept the new text of the item, save it as list item data. Quasi-semi-pseudo-code:

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

    I'm sorry if it looks too much like C++.

    0 讨论(0)
提交回复
热议问题