![](/img/trans.png)
[英]Assertion failure when assigning null to QObject* property of C++ type in QML
[英]Memory management for list of QObject* results in "Cannot read property X of null" errors in QML
我需要创建QObject*
的动态列表(表示自定义模型)并将它们公开给 QML。问题是 QML 试图重新使用以前删除的QObject*
,这最终会在运行时出现错误:
qrc:/MyWidget.qml:6: TypeError: Cannot read property 'value' of null
这是我的 model:
#include <QObject>
class SubModel : public QObject
{
Q_OBJECT
public:
SubModel(int value) : m_value(value) {}
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
int value() { return m_value; }
void setValue(int value)
{
m_value = value;
emit valueChanged();
}
signals:
void valueChanged();
private:
int m_value;
};
这是包含SubModel
列表的QAbstractListModel
(注意createSubModels()
方法):
#include <QAbstractListModel>
#include <QObject>
#include <QVariant>
#include <memory>
#include <vector>
class ModelList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRole
{
SubModelRole = Qt::UserRole
};
Q_ENUM(ModelRole)
void setSubModels(std::vector<std::unique_ptr<SubModel>> subModels)
{
beginResetModel();
m_subModels = std::move(subModels);
endResetModel();
}
int rowCount(const QModelIndex& parent = QModelIndex()) const override
{
return m_subModels.size();
}
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if (!index.isValid()) {
return {};
}
switch (role) {
case ModelRole::SubModelRole:
return QVariant::fromValue<SubModel*>(m_subModels[index.row()].get());
}
return {};
}
bool setData(const QModelIndex& index, const QVariant& value, int role) override
{
Q_UNUSED(index);
Q_UNUSED(value);
Q_UNUSED(role);
return false;
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles;
roles[ModelRole::SubModelRole] = "submodel";
return roles;
}
Q_INVOKABLE void createSubModels()
{
std::vector<std::unique_ptr<SubModel>> subModels;
for (int i = 0; i < rand() % 5 + 1; i++) {
subModels.push_back(std::make_unique<SubModel>(rand() % 100));
}
setSubModels(std::move(subModels));
}
private:
std::vector<std::unique_ptr<SubModel>> m_subModels;
};
这是我的 QML 小部件使用SubModel
实例(注意类型property
):
import QtQuick 2.12
import MyLib.SubModel 1.0
Text {
property SubModel subModel;
text: subModel.value
}
这是main.qml
文件:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 2.15
Window {
visible: true
Column {
Button {
text: "Create list"
onClicked: modelList.createSubModels();
}
Column {
Repeater {
model: modelList
//// This does generate errors
MyWidget {
subModel: model.modelData
}
//// This does generate errors
// Text {
// property var data: modelData
// text: data.value
// }
//// This does NOT generate errors!
// Text {
// text: modelData.value;
// }
}
}
}
}
最后,这是main.cpp
文件:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <iostream>
#include "models.h"
int main(int argc, char* argv[])
{
qmlRegisterUncreatableType<SubModel>(
"MyLib.SubModel", 1, 0, "SubModel", "This type can't be created in QML");
auto modelList = std::make_unique<ModelList>();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext* rootContext = engine.rootContext();
rootContext->setContextProperty("modelList", modelList.get());
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
第一次单击"Create list"
按钮时,它工作正常。 但第二次,初始SubModule
指针被删除,QML 尝试访问nullptr
的value
属性,导致警告。 然后替换m_subModels
并有效更新 GUI。 我不明白为什么 QML 在我发出beginResetModel()
信号时尝试访问 model。
我知道我可以更改ModelList
并使data()
返回实际的int
值而不是SubModel
指针,但这是不可接受的,因为我实际上在实际代码中有几个子模型(关注点分离)。
我想到的解决方案:
if submodel !== null
in QML 但这看起来不太好new
替换std::unique_ptr
但这会造成 memory 泄漏parent
级一起使用,但 memory 在退出之前将永远不会被释放QSharedPointer
与deleteLater()
一起使用,但这并不总是有效QSharedPointer
与setObjectOwnership(JavaScriptOwnership)
一起使用,但这看起来很老套(未测试)QList<QObject*>
而不是QAbstractListModel
但这会产生相同的错误QQmlListProperty
而不是QAbstractListModel
但这会产生相同的错误SubModel*
而不是重新创建列表,但这看起来很复杂我在这个问题上花了几天时间,但找不到合适的解决方案。 您有什么想法请保持我的“子模型”不变,并在重新创建列表时避免 QML 警告?
我在这里上传了所有项目文件以轻松重现问题(使用cmake
进行构建)。
编辑:问题似乎与我对property
的使用有关,因为如果我不将modelData
存储为property
,则不会出现错误。
您的代码似乎工作正常。 这就是 Repeater 的工作原理。
当你的 model 被重置时,Repeater 开始以相反的顺序一个一个地删除它的 QQuickItems。
// qtdeclarative/src/quick/items/qquickrepeater.cpp
if (d->model) {
// We remove in reverse order deliberately; so that signals are emitted
// with sensible indices.
for (int i = d->deletables.count() - 1; i >= 0; --i) {
if (QQuickItem *item = d->deletables.at(i)) {
if (complete)
emit itemRemoved(i, item);
d->model->release(item);
}
}
for (QQuickItem *item : qAsConst(d->deletables)) {
if (item)
item->setParentItem(nullptr);
}
}
根据 Repeater 的策略,项目可能不会被完全删除,而是汇集起来以备将来使用。
在那一刻 Item 本身仍然存在,但你的 model 数据不存在了。
Text {
property var data: modelData // modelData is undefined at the moment
text: data.value // "data" is undefined. You see the warning message
}
因此,修复警告消息的最佳方法是检查是否定义了 modelData。
Text {
property var data: modelData
text: data ? data.value : ""
}
由于@samdavydov 解释的原因,Repeater 中的项目在 model 重置期间保持不变,但是通过取消引用m_subModels
,SubModels 被删除(因为 unique_ptr)并且视图由于您提到的信号被destroyed
而无效。
通过在重置期间交换两个向量,旧的 SubModels 将在 memory 中停留一点,直到 function 退出,此时重置已经完成并且正在使用新的 SubModels:
void setSubModels(std::vector<std::unique_ptr<SubModel>> subModels)
{
beginResetModel();
m_subModels.swap(subModels);
endResetModel();
} //RAII deletes old SubModel at this point
在我提到将它们保存在 memory 之后找到交换,感谢你自己;-)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.