升级到 Qt 5.15 后,ListView 委托中的父级为空

2022-01-19 00:00:00 qt5 qt qtquick2 qml c++

具有最简单委托的 ListView 会产生大量警告 "21:35:31.911 警告 T#16084047 unknown - qrc:/main.qml:15: TypeError: Cannot read如果尝试设置它的委托 anchors 属性并滚动列表(这使得委托被销毁/创建),则属性 'left' of null".在 Qt 5.12 或 5.9 中并非如此.

A ListView with a most simple delegate produces lots of warnings "21:35:31.911 warning T#16084047 unknown - qrc:/main.qml:15: TypeError: Cannot read property 'left' of null" if trying to set it's delegate anchors property and scrolling the list (which makes delegates to be destroyed/created). It was not the case in Qt 5.12 or 5.9.

文件main.qml

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    ListView {
        anchors.fill: parent
        model: cppModel

        delegate: Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 50

            Text { text: model.itemName }
        }
    }
}

文件main.cpp:

#include <QAbstractListModel>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtGlobal>
#include <QQmlContext>

#include <iostream>

void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
    QString logLine = qFormatLogMessage(type, context, msg);
    std::cout << logLine.toStdString() << std::endl;
}

class CppModel: public QAbstractListModel {
    // QAbstractItemModel interface
public:
    virtual int rowCount(const QModelIndex &parent) const override { return 100; }
    virtual QVariant data(const QModelIndex &index, int role) const override {
        if (role == (Qt::DisplayRole + 1)) {
            return QString("Element %1").arg(index.row());
        }
        return QVariant();
    }
    virtual QHash<int, QByteArray> roleNames() const override {
        return {{(Qt::DisplayRole+1), "itemName"}};
    }
};

int main(int argc, char *argv[])
{
    qSetMessagePattern("%{time hh:mm:ss.zzz} %{type} T#%{threadid} %{function} - %{message}");
    qInstallMessageHandler(myMessageHandler);

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    CppModel cppModel;
    engine.rootContext()->setContextProperty("cppModel", &cppModel);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

链接到完整的源代码

我做错了什么以及如何为委托元素正确设置 anchors?

What I'm doing wrong and how to correctly set anchors for the delegate element?

推荐答案

这是 Qt 5.15 中行为更改的结果.第一个问题报告了这里,有更详细的总结这里.新文档 说:

This is the result of a behaviour change in Qt 5.15. The first issue was reported here, with a more detailed summary here. The new documentation says:

委托会根据需要进行实例化,并且可以随时销毁.因此,状态永远不应该存储在委托中.代表们是通常是 ListView 的 contentItem 的父级,但通常取决于无论它在视图中是否可见,父级都可以更改,并且有时为空.因此,绑定到父级的属性不建议从代表内部.如果你想要委托要填写 ListView 的宽度,请考虑使用以下之一改为以下方法:

Delegates are instantiated as needed and may be destroyed at any time. As such, state should never be stored in a delegate. Delegates are usually parented to ListView's contentItem, but typically depending on whether it's visible in the view or not, the parent can change, and sometimes be null. Because of that, binding to the parent's properties from within the delegate is not recommended. If you want the delegate to fill out the width of the ListView, consider using one of the following approaches instead:

ListView {
    id: listView
    // ...

    delegate: Item {
        // Incorrect.
        width: parent.width

        // Correct.
        width: listView.width
        width: ListView.view.width
        // ...
    }
}

所以,你可以:

  1. ListView 一个 id 并在绑定中使用它而不是 parent.
  2. 使用附加属性 (ListView.view) 访问视图.
  3. 检查是否为空(anchors.left: parent ? parent.left: undefined).
  1. Give the ListView an id and use it in the binding instead of parent.
  2. Use the attached property (ListView.view) to access the view.
  3. Check for null (anchors.left: parent ? parent.left : undefined).

选项 1 和 2 将产生更清晰的代码.

Options 1 and 2 will result in cleaner code.

选项 1 会减少一个 QObject 被创建(每个附加对象都是一个 QObject),但会将委托绑定到该特定视图.

Option 1 results in one less QObject being created (each attached object is a QObject) but ties the delegate to that particular view.

选项 2 更适合作为独立组件的委托,这些组件将与其他 ListView 一起重用.

Option 2 is better for delegates that are standalone components that will be reused with other ListViews.

相关文章