You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现同时兼容QML模型与C++模型的ListView委托

如何实现同时兼容QML模型与C++模型的ListView委托

刚好之前团队协作时也遇到过一模一样的需求——设计师用QML mock模型在Design Studio里做界面预览,开发侧用C++后端模型跑逻辑,还要保证同一个ListView委托能无缝切换两种模型。我来给你分享两个靠谱的解决思路,第一个是最贴合Qt模型视图架构的标准方案,推荐优先用这个~

方案一:把C++模型改成标准的QAbstractListModel子类(推荐)

你现在用的QList<Item*>暴露给QML后,QML会把它当成一个对象列表,每个元素只能通过modelData来访问;而QML的ListModel是基于**角色(Role)**的模型,委托可以直接通过角色名访问属性。所以核心问题是让C++模型也支持角色访问,和QML模型对齐。

具体做法是把原来的QList<Item*>换成QAbstractListModel的子类,定义对应的角色名,这样不管是C++模型还是QML模型,都能通过相同的角色名来访问属性。

修改后的C++代码(backend.h)

#ifndef BACKEND_H
#define BACKEND_H

#include <QObject>
#include <QAbstractListModel>
#include <QList>

// 单个数据项类,保持原有逻辑不变
class Item : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged)
public:
    Item(QString name, QObject *parent = nullptr)
        : QObject{parent}, m_name(name)
    {}

signals:
    void nameChanged();

private:
    QString m_name {"NULL"};
};

// 自定义列表模型,继承QAbstractListModel实现角色支持
class ItemListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    // 定义数据角色,和QML模型的属性名保持一致
    enum Roles {
        NameRole = Qt::UserRole + 1
    };

    explicit ItemListModel(QObject *parent = nullptr) 
        : QAbstractListModel(parent) {}

    // 实现模型行数统计
    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        if (parent.isValid()) return 0;
        return m_items.count();
    }

    // 根据角色返回对应数据
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid() || index.row() >= m_items.count()) return QVariant();
        const Item* item = m_items.at(index.row());
        switch (role) {
        case NameRole:
            return item->property("name");
        default:
            return QVariant();
        }
    }

    // 注册角色名,让QML能识别对应属性
    QHash<int, QByteArray> roleNames() const override {
        QHash<int, QByteArray> roles;
        roles[NameRole] = "name"; // 角色名和QML模型的属性名完全对齐
        return roles;
    }

    // 添加数据项的方法
    void addItem(Item* item) {
        beginInsertRows(QModelIndex(), m_items.count(), m_items.count());
        m_items.append(item);
        endInsertRows();
    }

private:
    QList<Item*> m_items;
};

// 后端类,现在暴露自定义的标准模型
class Backend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(ItemListModel* model MEMBER m_model NOTIFY modelChanged)
public:
    explicit Backend(QObject *parent = nullptr)
        : QObject{parent}, m_model(new ItemListModel(this))
    {
        // 初始化测试数据
        m_model->addItem(new Item {"Cpp"});
        m_model->addItem(new Item {"backend"});
        m_model->addItem(new Item {"is"});
        m_model->addItem(new Item {"great!"});
    }

    // 析构函数清理资源
    virtual ~Backend() override {
        for (Item* item : m_items) { // 若m_items为私有,需给ItemListModel添加getter或clear方法
            delete item;
        }
    }

signals:
    void modelChanged();

private:
    ItemListModel* m_model;
};

#endif // BACKEND_H

QML代码修改(Main.qml)

现在不管切换成QML模型还是C++模型,委托的写法完全一致,不用改任何代码:

import QtQuick

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

    ListModel {
        id: mockupList
        ListElement { name: "Hello" }
        ListElement { name: "World!" }
        ListElement { name: "How" }
        ListElement { name: "are" }
        ListElement { name: "you?" }
    }

    ListView {
        id: listView
        anchors.fill: parent
        anchors.margins: 20
        spacing: 10
        orientation: ListView.Vertical

        // 随便切换这两个模型,都能正常渲染
        model: mockupList
        // model: cppBackend.model

        delegate: MyListItem {
            required name // 统一用这种方式访问,和QML模型的写法完全一致
        }
    }
}

MyListItem.qml完全不用修改,保持你原来的代码即可。

这个方案的好处是完全遵循Qt的模型视图架构规范,支持后续的排序、过滤、动态增删数据等高级功能,而且和QML原生模型的访问方式100%对齐,是最优雅的解决方式。

方案二:在QML委托里做兼容逻辑(适合不想改C++代码的场景)

如果不想改动现有的C++模型结构,可以在委托里写一段兼容逻辑,自动判断是从modelData取属性还是直接取角色属性。

修改你的MyListItem.qml

import QtQuick

Item {
    id: myItem
    // 兼容两种模型的属性绑定逻辑
    property string name: {
        // 检查是否存在modelData(C++模型场景),存在则取modelData.name,否则取默认值
        if (typeof modelData !== 'undefined' && modelData) {
            return modelData.name;
        } else {
            return _defaultName;
        }
    }
    property string _defaultName: "Default Name"

    // 监听C++模型的属性变化(可选,若需要响应数据更新)
    Component.onCompleted: {
        if (modelData) {
            modelData.nameChanged.connect(() => name = modelData.name);
        }
    }

    Text {
        id: label
        text: myItem.name
        font.pointSize: 24
    }
}

然后在Main.qml的委托里,不用写required name,直接用:

delegate: MyListItem {}

这种方式不用改C++代码,但是如果有多个属性的话,每个属性都要写一遍兼容逻辑,而且不如方案一规范,适合临时快速兼容的场景。

总结

  • 优先选方案一,这是Qt官方推荐的模型视图用法,扩展性和可维护性最强,彻底解决兼容问题;
  • 方案二更适合不想动C++代码的临时场景,但是属性多了会比较繁琐。

备注:内容来源于stack exchange,提问作者Ben Pyton

火山引擎 最新活动