Как отменить изменение в QAbstractTableModel?

#python #pyqt #pyqt5 #undo #qabstracttablemodel

#python #pyqt #pyqt5 #отменить #qabstracttablemodel

Вопрос:

У меня есть этот простой пример: значение в последнем столбце моей QAbstractTableModel равно значению в столбце 1, умноженному на 2. Таким образом, каждый раз, когда вносится изменение в значение в столбце 1, это приводит к изменению в столбце 2.

Когда значение в последнем столбце изменилось — отображается окно сообщения с вопросом, желает ли пользователь подтвердить действие.

Представьте, что пользователь изменил значение в столбце 1 и видит это сообщение: Я хочу, чтобы мое приложение отменяло изменения, если нажата кнопка Отмена (возвращает оба старых значения в столбце 1 и 2), вы можете мне с этим помочь? Мне нужно определить мою функцию ‘go_back()’:

 class Mainwindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()
        self.setCentralWidget(self.table)

        self.data = [
            [1, 0.18, 0.36],
            [2, 0.25, 0.50],
            [3, 0.43, 0.86],
            ]

        self.model = MyModel(self.data)
        self.table.setModel(self.model)

        self.table.setSelectionBehavior(self.table.SelectRows)
        self.table.setSelectionMode(self.table.SingleSelection)

        self.model.dataChanged.connect(lambda index: self.count_last_column(index))
        self.model.dataChanged.connect(lambda index: self.if_last_column_changed(index))

    def calculations(self, position):
        value = self.model.list_data[position][1] * 2
        return value

    def count_last_column(self, index):
        if index.column() == 1:
            position = index.row()
            self.model.setData(self.model.index(position,2), self.calculations(position))

    def if_last_column_changed(self, index):
        if index.column() == 2:
            message_box, message_box_button = self.show_message_box()

            if message_box_button == 'Ok':
                pass
            elif message_box_button == 'Cancel':
                self.go_back()

    def show_message_box(self):
        self.message_box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning, 'Action', 'Value in column 3 has changed, confirm action?')

        self.message_box.Ok = self.message_box.addButton(QtWidgets.QMessageBox.Ok)
        self.message_box.Cancel = self.message_box.addButton(QtWidgets.QMessageBox.Cancel)

        self.message_box.exec()

        if self.message_box.clickedButton() == self.message_box.Ok:
            return (self.message_box, 'Ok')
        elif self.message_box.clickedButton() == self.message_box.Cancel:
            return (self.message_box, 'Cancel')

    def go_back(self):
        pass #################



class MyModel(QtCore.QAbstractTableModel):

    def __init__(self, list_data = [[]], parent = None):
        super(MyModel, self).__init__()
        self.list_data = list_data

    def rowCount(self, parent):
        return len(self.list_data)

    def columnCount(self, parent):
        return len(self.list_data[0])

    def data(self, index, role):

        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.list_data[row][column]
            return value

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            value = self.list_data[row][column]
            return value


    def setData(self, index, value, role = QtCore.Qt.EditRole):

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            self.list_data[row][column] = value
            self.dataChanged.emit(index, index)
            return True

        return False

    def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable


if __name__ == '__main__':

    app = QtWidgets.QApplication([])
    application = Mainwindow()
    application.show()


    sys.exit(app.exec())
  

Ответ №1:

Один из вариантов — использовать QUndoStack с моделью элемента и помещать объекты QUndoCommand в стек в setData . Преимущество этого подхода в том, что он упрощает реализацию дополнительных элементов управления отменой / повтором в будущем, если вы этого захотите.

В MyModel просто создайте стек в конструкторе и добавьте строку для отправки команды в стек прямо перед изменением данных списка (чтобы предыдущее значение можно было сохранить в команде). Остальная часть класса не изменилась.

 class MyModel(QtCore.QAbstractTableModel):

    def __init__(self, list_data = [[]], parent = None):
        super(MyModel, self).__init__()
        self.list_data = list_data
        self.stack = QtWidgets.QUndoStack()

    def setData(self, index, value, role = QtCore.Qt.EditRole):

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            
            self.stack.push(CellEdit(index, value, self))
            
            self.list_data[row][column] = value
            self.dataChanged.emit(index, index)
            return True

        return False
  

Создайте QUndoCommand с индексом, значением и моделью, переданными конструктору, чтобы желаемая ячейка могла быть изменена вызовами undo или redo.

 class CellEdit(QtWidgets.QUndoCommand):

    def __init__(self, index, value, model, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.index = index
        self.value = value
        self.prev = model.list_data[index.row()][index.column()]
        self.model = model

    def undo(self):
        self.model.list_data[self.index.row()][self.index.column()] = self.prev

    def redo(self):
        self.model.list_data[self.index.row()][self.index.column()] = self.value
  

И теперь все, что нужно сделать в go_back , это дважды вызвать метод отмены для обеих ячеек, которые были изменены.

 def go_back(self):
    self.model.stack.undo()
    self.model.stack.undo()
  

Комментарии:

1. Неплохо. Я даже не знал о QUndoxxx.

2. Префект! Большое вам спасибо!