Добавление строки в QTableView с виджетами модели и делегата

Я пытаюсь добавить строку в QTableView с помощью QAbstractTableModel и QItemDelegate, где виджеты появляются в добавленной строке. Из того, что я прочитал, мне нужно вызвать .edit (index) для каждого элемента добавленной строки, чтобы вызвать createEditor, в котором создаются виджеты, однако я получаю edit: editing failed

QItemDelegate:

class Delegate(QItemDelegate):

    def __init__(self):
        QItemDelegate.__init__(self)

        self.type_items = ["1", "2", "3"]

    def createEditor(self, parent, option, index):

        # COMBOBOX, LINEEDIT, TIMEDIT
        if index.column() == 0:
            comboBox = QComboBox(parent)
            for text in self.type_items:
                comboBox.addItem(text, (index.row(), index.column()))
            return comboBox

        elif index.column() == 1:
            lineEdit = QLineEdit(parent)
            return lineEdit

        elif index.column() == 2:
            timeEdit = QTimeEdit(parent)
            return timeEdit

    def setEditorData(self, editor, index):
        value = index.model()._data[index.row()][index.column()]

        if index.column() == 0:
            editor.setCurrentIndex(self.type_items.index(value))

        elif index.column() == 1:
            editor.setText(str(value))

        elif index.column() == 2:
            editor.setTime(value)

QAbstractTableModel:

class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data

    def data(self, index, role):
        pass

    def rowCount(self, index=None):
        return len(self._data)

    def columnCount(self, index=None):
        return len(self._data[0])

Главный:

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        localWidget = QWidget()

        self.table = QTableView(localWidget)

        data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]

        self.model = TableModel(data)
        self.table.setModel(self.model)
        self.table.setItemDelegate(Delegate())

        self.add_row = QPushButton("Add Row", localWidget)
        self.add_row.clicked.connect(self.addRow)

        for row in range(self.model.rowCount()):
            for column in range(self.model.columnCount()):
                index = self.model.index(row, column)
                self.table.openPersistentEditor(index)

        layout_v = QVBoxLayout()
        layout_v.addWidget(self.table)
        layout_v.addWidget(self.add_row)
        localWidget.setLayout(layout_v)
        self.setCentralWidget(localWidget)
        self.show()

    def addRow(self):

        new_row_data = ["3", "Howdy", QTime(9, 0)]

        self.model.beginInsertRows(QModelIndex(), self.model.rowCount(), self.model.rowCount())
        self.model._data.append(new_row_data)
        self.model.endInsertRows()
        for i in range(len(new_row_data)):
            index = self.table.model().index(self.model.rowCount()-1, i)
            self.table.edit(index)

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Как я могу вызвать createEditor QItemDelegate для создания виджетов для элементов добавленной строки и setEditorData для их заполнения?


person Zoes    schedule 28.01.2021    source источник


Ответы (1)


Прежде всего, вы можете использовать openPersistentEditor, как вы уже делали в __init__.

Есть две причины ошибки в self.table.edit():

  1. чтобы разрешить редактирование элемента с помощью edit(), функция flags() необходимо переопределить и предоставить флаг Qt.ItemIsEditable;
  2. вы не можете вызывать edit() несколько раз для разных индексов в одной и той же функции;

Затем в вашем коде есть другие важные проблемы:

  • вы неправильно используете модель, поскольку data() не реализована;
  • делегаты должны использовать базовые функции модели всякий раз, когда они обеспечивают стандартное поведение (доступ к index.model()._data плохой);
  • setEditorData не требуется, пока соблюдаются указанные выше аспекты: Qt автоматически устанавливает данные на основе типа данных и класса редактора;
  • setData() необходимо реализовать, чтобы правильно установить данные в модели, иначе новые данные будут недоступны;
  • изменения в структуре модели (например, создание новой строки) должны производиться в классе модели;

Вот исправленная версия вашего кода:

class Delegate(QItemDelegate):
    def __init__(self):
        QItemDelegate.__init__(self)
        self.type_items = ["1", "2", "3"]

    def createEditor(self, parent, option, index):
        if index.column() == 0:
            comboBox = QComboBox(parent)
            for text in self.type_items:
                comboBox.addItem(text, (index.row(), index.column()))
            return comboBox
        # no need to check for the other columns, as Qt automatically creates a
        # QLineEdit for string values and QTimeEdit for QTime values;
        return super().createEditor(parent, option, index)

    # no setEditorData() required


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data

    def appendRowData(self, data):
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self._data.append(data)
        self.endInsertRows()

    def data(self, index, role=Qt.DisplayRole):
        if role in (Qt.DisplayRole, Qt.EditRole):
            return self._data[index.row()][index.column()]

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole:
            self._data[index.row()][index.column()] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def rowCount(self, index=None):
        return len(self._data)

    def columnCount(self, index=None):
        return len(self._data[0])

    def flags(self, index):
        # allow editing of the index
        return super().flags(index) | Qt.ItemIsEditable


class MainWindow(QMainWindow):
    # ...
    def addRow(self):
        row = self.model.rowCount()

        new_row_data = ["3", "Howdy", QTime(9, 0)]
        self.model.appendRowData(new_row_data)

        for i in range(self.model.columnCount()):
            index = self.model.index(row, i)
            self.table.openPersistentEditor(index)
person musicamante    schedule 28.01.2021
comment
setData () должен быть реализован для правильной установки данных в модели, иначе новые данные будут недоступны. Как вызвать setData () при изменении поля со списком? - person Zoes; 29.01.2021
comment
@Zoes, которые зависят от того, как вы хотите, чтобы редактор вел себя и какие данные вы хотите установить. Обычно Qt автоматически вызывает setData(), используя значение, основанное на типе данных и редакторе (как и setEditorData()), когда изменяется текущий индекс, и это происходит потому, что setModelData вызывается в делегате. Если вы хотите изменить результат, вы должны реализовать setModelData или связать сигнал измененного значения редактора с соответствующей функцией, которая в конечном итоге вызывает setData(): в вашем случае это может быть необходимо для поля со списком, используя один из его сигналов. - person musicamante; 29.01.2021
comment
Имейте в виду, что, поскольку вы устанавливаете пользовательские данные для элемента combobox, вам, вероятно, также необходимо правильно реализовать setEditorData: ваш data() возвращает простую строку, но вы используете пользовательские данные для комбинированного списка, и вам может потребоваться выбрать соответствующий элемент, иначе реализация по умолчанию попытается установить индекс на основе своего свойства по умолчанию (обычно путем поиска первого совпадения currentText). Я настоятельно рекомендую вам внимательно изучить документацию делегатов и моделей, провести несколько экспериментов и в конечном итоге создать новый вопрос, если вам нужны пояснения. - person musicamante; 29.01.2021