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

Parent is null in ListView delegate after upgrade to Qt 5.15

具有最简单委托的 ListView 会产生很多警告 "21:35:31.911 warning T#16084047 unknown - qrc:/main.qml:15: TypeError: Cannot read property 'left' of null" 如果尝试设置它的委托 anchors 属性 并滚动列表(这使得委托是 destroyed/created)。在 Qt 5.12 或 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();
}

Link to full source code

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

这是 Qt 5.15 行为变化的结果。第一个问题被报道 here, with a more detailed summary here. The new documentation 说:

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 和 2 将使代码更简洁。

选项 1 导致少创建一个 QObject(每个附加对象都是一个 QObject)但将委托绑定到该特定视图。

选项 2 更适合作为将与其他 ListView 重用的独立组件的委托。