TableView и QAbstracTableModel при вызове QQmlApplicationEngine из другого класса

Я пытаюсь сделать модель QAbstractTableModel в cpp и подключиться к qml.

Этот код работает хорошо.

MyModel.h

#ifndef MYMODEL_H
#define MYMODEL_H

#include <QAbstractTableModel>

class MyModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    enum AnimalRoles {
        TypeRole = Qt::UserRole + 1,
        SizeRole
    };

    explicit MyModel(QObject *parent = nullptr);

    // Basic functionality:
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

protected:
    QHash<int, QByteArray> roleNames() const;

private:
    QList<Animal> m_animals;
};

#endif // MYMODEL_H

MyModel.cpp

#include "MyModel.h"
#include <QDebug>

MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
{
        qDebug() << __FUNCTION__;
        addAnimal(Animal("Wolf", "Medium"));
        addAnimal(Animal("Polar bear", "Large"));
        addAnimal(Animal("Quoll", "Small"));

}

int MyModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_animals.size();
}

int MyModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return 2;
}

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    qDebug() << __FUNCTION__ << index.row() << index.column() << role;
    if (!index.isValid())
        return QVariant();
    const Animal &animal = m_animals[index.row()];
    switch (role) {
    case TypeRole:
        return animal.type();
    case SizeRole:
        return animal.size();
    default:
        break;
    }

    return QVariant();
}

void MyModel::addAnimal(const Animal &animal)
{
    qDebug() << __FUNCTION__;
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    m_animals << animal;
    endInsertRows();
}

QHash<int, QByteArray> MyModel::roleNames() const
{
    qDebug() << __FUNCTION__;
    QHash<int, QByteArray> roles;
    roles[TypeRole] = "type";
    roles[SizeRole] = "size";
    return roles;
}

main.cpp

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

        MyModel model;

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("myModel", &model);
    engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

main_test.qml

import QtQuick 2.0
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4

Window {
    id: main_view
    width: 250
    height: 600
    visible: true

    ListView {
        id: list_view
        width: 200; height: 250

        model: myModel
        delegate: Text { text: "Animal Test: " + type + ", " + size }
    }


    TableView {
        id: table_view
        objectName: "tableView"
        width: 250; height: 250
        anchors.top:  list_view.bottom
        model: myModel

        TableViewColumn {
            id: type_col
            role: "type"
            title: "Type"
            width: 100
        }
        TableViewColumn {
            id: size_col
            role: "size"
            title: "Size"
            width: 100
        }
    }

}

Это выглядит так

Но, если я немного изменю main.cpp, представление списка будет отображаться как обычно, но не представление таблицы.

main.cpp

#include "MainView.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    MainView mainView;
    return app.exec();
}

MainView.h

#ifndef MAINVIEW_H
#define MAINVIEW_H

#include <QObject>
#include <QQmlApplicationEngine>

class MainView: public QObject
{
    Q_OBJECT
public:
   explicit  MainView(QObject *parent=nullptr);
    void initializeView();

private:
    QQmlApplicationEngine m_engine;
};

#endif // MAINVIEW_H

MainView.cpp

#include "MainView.h"
#include "MyModel.h"
#include <QQmlContext>

MainView::MainView(QObject *parent)
    : QObject(parent)
{
    initializeView();
}

void MainView::initializeView()
{
    MyModel model;
    m_engine.rootContext()->setContextProperty("myModel", &model);
    m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));

}

Похоже на это.

Я действительно не понимаю, почему это происходит. Какая разница между ListView и TableView во втором случае? И как это исправить, т.е. сделать отображение данных во втором случае? Заранее спасибо.

1 ответ

Решение

Проблема в initializeView Модель является локальной переменной в этой области, и каждая локальная переменная удаляется из памяти, когда функция завершает выполнение. В первом случае модель будет исключена при закрытии приложения, во втором случае она будет исключена при initializeView заканчивается, то есть когда отображается окно, есть 2 возможных решения:

  • Создать указатель и управлять памятью, которую мы передаем MainView как родитель, так что он удаляет его из памяти (это последнее свойство QObject s):

void MainView::initializeView()
{
    MyModel *model = new MyModel(this);
    m_engine.rootContext()->setContextProperty("myModel", model);
    m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));

}
  • Сделайте модель членом класса.

*.час

#ifndef MAINVIEW_H
#define MAINVIEW_H

#include <QObject>
#include <QQmlApplicationEngine>

class MainView: public QObject
{
    Q_OBJECT
public:
   explicit  MainView(QObject *parent=nullptr);
    void initializeView();

private:
    QQmlApplicationEngine m_engine;
    MyModel model{this};
};

#endif // MAINVIEW_H

*.cpp

...

void MainView::initializeView()
{
    m_engine.rootContext()->setContextProperty("myModel", &model);
    m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));

}

Чтобы понять поведение, я добавил еще несколько впечатлений:

MyModel::~MyModel()
{
    qDebug()<<"destructor";
}

TableView {
    ...
    Component.onCompleted: {
        console.log("completed table")
        if(!timer.running)
            timer.running = true
    }
}

ListView {
    ...
    Component.onCompleted: {
        console.log("completed list")
        if(!timer.running)
            timer.running = true
    }
}


Timer {
    id: timer
    interval: 0;
    onTriggered:  console.log(myModel, table_view.model, list_view.model)
}

И я получаю следующее:

MyModel
addAnimal
addAnimal
addAnimal
roleNames
data 0 0 257
data 0 0 258
data 1 0 257
data 1 0 258
data 2 0 257
data 2 0 258
roleNames
qml: completed list
data 0 0 257
data 0 0 258
qml: completed table
destructor
qml: null null null

Мы отмечаем, что ListView удается загрузить данные, тогда как TableView в середине нагрузки называется деструктор.

Возможное объяснение: я думаю, что ListView хранит данные, создавая их копию и обновляя их только при уведомлении модели; также следует уведомлять об удалении модели для очистки данных; похоже, это ошибка. С другой стороны, TableView Находясь в момент загрузки и удаления модели, имеет значение null, поэтому он уведомляется и очищает данные.

Делать еще один тест:

void MainView::initializeView()
{
    MyModel *model = new MyModel;
    m_engine.rootContext()->setContextProperty("myModel", model);
    m_engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    QTimer::singleShot(1000, model, &MyModel::deleteLater);
}

Замечено, что оба загружают данные правильно, и через секунду модель уничтожается, но только тот, который уведомлен, является TableView так как это единственный, который очищает показанные данные, и ListView не.

мой вывод, что это ListView ошибка.

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