Как установить пользовательскую модель класса QAbstractTableModel в QML TableView

Я создал пользовательскую табличную модель Python class, создав подкласс QAbstractTableModel, и когда я пытаюсь установить экземпляр этого класса, модели, в свойство modelTableView, все приложение падает. В терминале нет информации об отладке ошибок о том, что вызывает сбой. Я использую QML версии Qt 6.4 и PySide6.

код:

main.py:

# This Python file uses the following encoding: utf-8
import sys
import os
from PySide6.QtCore import QUrl, QObject, Slot, Qt, QAbstractTableModel
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, QJSValue, qmlRegisterType
import create_table_model_E1


class Comm(QObject):
    '''
    Object - slot-owner and signal acceptor
    '''

    def __init__(self):
        super().__init__()

    # Signal reciever for signal rowColData from qml that contains header data for table
    @Slot(QJSValue, QJSValue)
    def handle_row_col_data(self, row_names: QJSValue, col_names: QJSValue):
        row_names = row_names.toVariant()
        col_names = col_names.toVariant()
        print("Signal received from QML - Row Names:", row_names)
        print("Signal received from QML - Column Names:", col_names)
        print("Creating Table Model..")
        model = create_table_model_E1.create_model(row_names, col_names, False)
        if isinstance(model, QAbstractTableModel):
            tableView.setProperty("model", model)
            # pass

## tried, importing it in qml file doesnt work
# # Register the CustomTableModel with QML
# sys.path.append('create_table_model_E1.py')
# qmlRegisterType(create_table_model_E1.CustomTableModel, "myCustomTableModel", 1, 0,
#                                        "MyCustomTableModel")
##

if __name__ == '__main__':
    # Create a QApplication instance
    app = QGuiApplication(sys.argv)

    # Get the absolute path to the QML file
    qml_file = os.path.abspath('content/App.qml')

    # Reciever class
    com = Comm()

    # Create a QQmlApplicationEngine instance
    engine = QQmlApplicationEngine()

    # Load the main QML file
    engine.load(QUrl.fromLocalFile(qml_file))
    
    qml_obj = engine.rootObjects()[0]
    
    # find the object that emits the signal containing the data
    rowColData_comp = qml_obj.findChild(QObject, "data_to_table")
    rowColData_comp.rowColData.connect(com.handle_row_col_data,
                                       type=Qt.ConnectionType.QueuedConnection)

    # find the tableview component
    tableView = qml_obj.findChild(QObject, "TableView")

    # If the rootObjects() method of the QQmlApplicationEngine
    # instance returns an empty list,
    # it means the QML file could not be loaded, so exit the
    # application with a status code of -1
    if not engine.rootObjects():
        sys.exit(-1)

    # Start the main event loop of the application by calling app.exec()
    sys.exit(app.exec())

create_table_model_E1.py:

class CustomTableModel(QAbstractTableModel):

    dataChanged = Signal(QModelIndex, QModelIndex, list)

    def __init__(self, data, headers, parent=None) -> None:
        super(CustomTableModel, self).__init__(parent)
        self._data = data
        self._headers = headers

    def rowCount(self, parent=None) -> int:
        # Return the number of rows in the table
        return len(self._data)

    def columnCount(self, parent=None) -> int:
        # Return the number of columns in the table
        return len(self._data[0])

    def data(self, index: Union[QModelIndex, QPersistentModelIndex], role=Qt.DisplayRole) -> Any:
        # Return the data for a specific index and role
        if not index.isValid():
            return None

        row = index.row()
        col = index.column()

        if role == Qt.DisplayRole:
            # Return the display data for the cell
            return self._data[row][col]

    def headerData(self, section, orientation: Qt.Orientation, role=Qt.DisplayRole) -> Any:
        # Return the header data for a specific section and role
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                # Return the horizontal header data
                return self._headers[section]
            elif orientation == Qt.Vertical:
                # Return the vertical header data
                return str(section + 1)

    # ... editable model methods ...
    def setData(self, index: Union[QModelIndex, QPersistentModelIndex], value: Any, role: int = Qt.EditRole) -> bool:  # noqa
        if role == Qt.EditRole:
            row = index.row()
            column = index.column()
            if 0 <= row < self.rowCount() and 0 <= column < self.columnCount():
                # Update the data in the internal data structure
                self._data[row][column] = value
                # Emit dataChanged signal to notify the view
                self.dataChanged.emit(index, index, [role])
                return True
        return False

    def flags(self, index: Union[QModelIndex, QPersistentModelIndex]) -> Qt.ItemFlag:
        default_flags = super().flags(index)
        if index.isValid():
            # Set the item flags to be editable
            return default_flags | Qt.ItemIsEditable
        return default_flags

    # ... other methods ...

    def insertRows(self, row, count, parent=QModelIndex()) -> bool:
        self.beginInsertRows(parent, row, row + count - 1)
        for _ in range(count):
            empty_row = [' '] * self.columnCount()
            self._data.insert(row, empty_row)
        self.endInsertRows()
        return True

    def insertColumns(self, column, count, parent=QModelIndex()) -> bool:
        self.beginInsertColumns(parent, column, column + count - 1)
        for _ in range(count):
            for row in self._data:
                row.insert(column, ' ')
            self._headers.insert(column, ' ')
        self.endInsertColumns()
        return True

    def removeRows(self, row, count, parent=QModelIndex()) -> bool:
        self.beginRemoveRows(parent, row, row + count - 1)
        for _ in range(count):
            self._data.pop(row)
        self.endRemoveRows()
        return True

    def removeColumns(self, column, count, parent=QModelIndex()) -> bool:
        self.beginRemoveColumns(parent, column, column + count - 1)
        for row in self._data:
            for _ in range(count):
                row.pop(column)
            self._headers.pop(column)
        self.endRemoveColumns()
        return True

    def getAllData(self) -> list:
        """
        Return all data in the model.
        """
        return self._data


def create_model(rowsn: list, colsn: list, isEl3: bool = True) -> QAbstractTableModel:

    rownames = ['names']
    colnames = ['names']

    for col in colsn:
        colnames.append(col)
    for row in rowsn:
        rownames.append(row)

    rownames.append('Weights')
    if isEl3:
        rownames.append('Indifference(q)')
        rownames.append('Preference(p)')

    rownames.append('Veto')

    data = []
    temp = []

    for rid, row in enumerate(rownames):
        for id, col in enumerate(colnames):
            if rid == 0:
                temp.append(col)
            else:
                if id == 0:
                    temp.append(row)
                else:
                    temp.append(' ')
        data.append(copy(temp))
        temp.clear()

    model = CustomTableModel(data, data[0])

    return model

Таблица.qml:

import QtQuick
import QtQuick.Controls 2.15
// import myCustomTableModel 1.0 doesnt work

Item {
    id: root
    width: 400
    height: 200
    property var colNames: ["names", "cr1", "cr2", "cr3"]
    property var tableSize: [view.rowHeightProvider, view.columnWidthProvider]

    TableView {
        id: view
        objectName: "TableView"
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: parent.bottom
        boundsBehavior: Flickable.StopAtBounds
        focus: true
        interactive: true
        anchors.topMargin: 0
        rowSpacing: -1
        columnSpacing: -1
        clip: false
        property bool tabpressed: false
        property bool returnpressed: false
        property bool uppressed: false
        property bool downpressed: false
        property bool leftpressed: false
        property bool rightpressed: false

        // user-specific table model
        property var userTableModel: null

        rowHeightProvider: function (index) {
            return 30
        }
        columnWidthProvider: function (index) {
            return 100
        }

        selectionModel: ItemSelectionModel {
            id: itemSelectionModel
            model: view.model
        }

        model: TableModelCustom {  // dummy model
            id: model
        }

        delegate: Table_customDelegate_FROM_UPWORK {
            id: viewdelegate
            width: 100
            height: 30
        }

        onUserTableModelChanged: {
            console.info("[Table_custom.qml]: USER TABLE MODEL CHANGED")
            if (view.userTableModel != null) {
                view.model = view.userTableModel
                console.info("NEW MODEL: " + view.userTableModel)
             // SOMEWHERE HERE IT CRASHES
            }
        }
    }

    HorizontalHeaderView {
        id: horizontalheader
        x: 0
        y: 0

        width: view.width
        height: 30
        boundsBehavior: Flickable.StopAtBounds
        interactive: true
        clip: true

        model: colNames

        delegate: Item {
            id: wrapper
            implicitWidth: 100
            implicitHeight: 30

            Rectangle {
                id: background
                color: "#dbdbdb"
                border.color: "#000000"
                border.width: 1
                anchors.fill: parent

                Rectangle {
                    id: rectangle
                    x: 1
                    width: 1
                    color: index === 0 ? "#222222" : "#00ffffff"
                    border.width: 0
                    anchors.top: parent.top
                    anchors.bottom: parent.bottom
                    anchors.bottomMargin: 0
                    anchors.topMargin: 0
                }
            }

            Text {
                id: text1
                color: "#3b3b3b"
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
                anchors.rightMargin: 2
                anchors.bottomMargin: 2
                anchors.topMargin: 2
                text: horizontalheader.model[row]
                anchors.fill: parent
            }
        }
        syncView: view
    }
}

Я попытался создать модель, вернуть ее в main.py, а затем передать в TableView, используя setProperty, напрямую или установив property var userTableModel, а затем установив это в свойство modelTableView, но когда модель изменяется, все приложение падает. Все это делается во время выполнения. Я проверил переменную tableView в main.py, и это не тип виджета (QTableView), это QuickItemType (tableView.isQuickItemType() возвращает True), и это все, что я мог найти об этом. Что мне не хватает? Почему при смене модели с существующей на вновь созданную приложение вылетает?

Подводя итог: я хочу изменить модель TableView во время выполнения, и когда я это делаю, приложение вылетает.

Извините за длинные блоки кода.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
97
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как насчет того, чтобы зарегистрировать класс модели как тип и просто использовать его в файле qml?

Вызов

qmlRegisterType(CustomTableModel, 'CustomTableModel', 1, 0, 'CustomTableModel')

в вашем main.py.

Затем вы можете импортировать этот тип в файл Table.qml и использовать модель:

import CustomTableModel

Нет необходимости в дополнительном свойстве userTableModel. Если вы хотите заполнить данные на стороне Python, зарегистрируйте экземпляр модели как синглтон с помощью qmlRegisterSingletonInstance.

Привет, я пробовал, но не знаю, почему это не сработало. Редактирование кода qml (импорт и использование) в создателе qt или студии дизайна не может распознать новый зарегистрированный тип, и, конечно, это приводит к невозможности даже запустить все приложение.

Nick V 13.04.2023 17:08

Кроме того, мне не просто нужно заполнить данные на стороне Python, я также хочу изменить свойство модели, которое установлено на фиктивную модель, и изменить его на модель, возвращаемую из пользовательского подкласса в Python.

Nick V 13.04.2023 17:15

Я не могу сказать, почему Qt Creator не знает ваш тип. Иногда помогает сброс модели кода QML, потому что кэширование типов немного неуклюже. Какой тип ошибки вы получаете?

Jürgen Lutz 17.04.2023 07:45

Вы можете добавить свойство модели в качестве псевдонима модели внутренней таблицы. Чем более последовательно менять модель (псевдоним свойства tableModel: view.model)

Jürgen Lutz 17.04.2023 07:47

Спасибо за ответ. Из того, что вы сказали, у меня появилась еще одна идея: не изменять модель, назначая новую во время выполнения, а сначала устанавливая пустую модель из пользовательского класса модели, а затем просто обновляя данные. Кажется, что-то (pyside, движок qml или что-то еще) ненавидит такие изменения. Таким образом, установка пустой модели, а затем ее изменение вместо создания и повторного назначения модели помогает, но у меня такое ощущение, что это скорее обходной путь, чем правильное использование фреймворка.

Nick V 17.04.2023 21:24

Кроме того, делая то, что я описал в предыдущем комментарии, движок автоматически распознает тип, который я объявляю. Странные вещи, я знаю

Nick V 17.04.2023 21:28

Другие вопросы по теме