如何创建用于 QML 的通用对象模型?

时间:2023-01-23
本文介绍了如何创建用于 QML 的通用对象模型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有任何宏或方法如何将Qt模型注册为QObject的属性.

I would like to know if there is any macro or way how to register Qt model as property of QObject.

例如,我有 AnimalModel (http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel).

我知道我可以将它传递给 QuickView 的根上下文

I Know I can pass it to root context of QuickView

QuickView view;
view.rootContext()->setContextProperty("myModel", &model);

如果我通过 Qml 宏注册了 QObject,我也可以传递这个对象来查看:

In case I have QObject registered via Qml macros, I can pass this object to view too:

view.rootContext()->setContextProperty("obj", pDataObject);

但是如果我想要拥有任何数据模型的 QObject 怎么办?

But what If I want to have QObject which holds model of any data?

例如:

class DataObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
    ...

    AnimalModel m_modelAnimals;

    //Is this possible in any way?
    //Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)
};

到目前为止,我发现的每个示例都展示了如何将 QAbstractListModel 传递给根上下文.但没有如何将其用作 QObject 属性.

Every example I found until now shows how to pass QAbstractListModel to root context. But none how to use it as QObject property.

(我知道有 QQmlListPropertyQQmlListProperty 不支持部分刷新.重建所有 Qml 对象总是必要的)

(I know there is QQmlListProperty but QQmlListProperty doesn't support partial refresh. It's always necessary to rebuild all Qml objects)

推荐答案

//Is this possible in any way?
//Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)

是的,你没有尝试过吗?当然,它不会是AnimalModel,而是AnimalModel *,但只要模型继承了QAbstractListModel,就够了.您甚至不需要 NOTIFY 部分,因为无论如何都会自动反映模型内部的更改.modelAnimalsChanged 仅在您用不同的模型替换整个模型时才有意义,并且自然而然地关闭 QML 关于在没有通知信号的情况下使用属性的警告.当模型对象没有改变时,执行后者的一种更简洁的方法是从插槽或 Q_INVOKABLE 返回一个 AnimalModel *.

Yes it is, didn't you try? Of course, it will not be a AnimalModel but a AnimalModel *, but as long as the model inherits QAbstractListModel, that's all you need. You don't even need the NOTIFY part, as changes, internal to the model will be automatically reflected anyway. modelAnimalsChanged only makes sense when you replace the entire model with a different model, and naturally, to shut up QML's warnings about using a property without a notify signal. A cleaner way to do the latter when the model object doesn't change is to just return a AnimalModel * from a slot or a Q_INVOKABLE.

如果你想要一个真正灵活的模型,你可以制作一个存储 QObject * 的模型,然后你可以从 QML 创建具有任意属性的任意对象,并添加到模型中.然后从模型中,您有一个返回对象的 object 角色,您可以查询和使用该对象来检索它所拥有的属性.经典"列表模型实现将定义一个具有静态、固定模式的模型,而使用这种方法允许在模型中拥有具有不同属性的无定形"对象.

If you want a truly flexible model, you can make one that stores QObject *, then from QML you can create arbitrary objects with arbitrary properties, and add to the model. Then from the model you have a single object role which returns the object, and you can query and use the object to retrieve the properties it holds. Whereas a "classical" list model implementation will define a model with a static, fixed schema, using this approach allows to have "amorphous" objects in the model with different properties.

自然,这需要某种类型安全性,例如为此类模型中的每个对象都有一个 property int type,然后您可以根据它确定对象的可用属性.我通常的方法是为委托提供一个 Loader,并将对象作为数据源传递给不同的 QML UI 实现,以可视化它实例化的对象类型.这样一来,模型中就有不同的对象,也有不同的 QML 项作为视图委托.

Naturally, this requires some type safety, for example have a property int type for each object in such a model, and based on it you can determine the available properties for the object. My usual approach is to have a Loader for a delegate, and have it pass the object as a data source to different QML UI implementations visualizing that object type that it instantiates. This way you have both different objects in the model, and different QML items as view delegates.

制作终极万事通"列表/模型对象的最后一步是实现 QQmlListPropertyQ_CLASSINFO("DefaultProperty", "container")它允许您动态地组合列表/模型,或使用 QML 的声明性语法.另请注意,使用此解决方案,您可以在此类模型中添加或删除,甚至删除声明式实例化对象.

The last step to making the ultimate "jack of all trades" list/model object is to implement QQmlListProperty and Q_CLASSINFO("DefaultProperty", "container") for it, allowing you to both compose the list/model dynamically, or using QML's declarative syntax. Also note that with this solution, you can add to or remove from such a model, even remove declaratively instantiated objects.

此外,根据您的使用场景,您可能需要为模型使用 qmlRegisterType()qmlRegisterUncreatableType().

Also, depending on your usage scenario, you may have to either qmlRegisterType() or qmlRegisterUncreatableType() for the model.

好吧,乍一看,任何数据的模型"似乎并不是指无模式模型,而是指不同的模式模型.在这种情况下,您可以使用 QAbstractListModel * 甚至 QObject * 而不是返回 AnimalModel * - 它无论如何都可以在 QML 中工作,因为它通过元系统使用动态.但无论如何,无模式模型更加强大和灵活,它们不需要定义 C++ 代码,它可以单独使用 QML 来工作.

OK, on a second glance, it looks like by "model of any data" you didn't mean schema-less models but simply different schema models. In that case, instead of returning an AnimalModel *, you can use a QAbstractListModel * or even a QObject * - it will work in QML anyway, as it employs dynamism through the meta system. But at any rate, schema-less models are that much more powerful and flexible, and they don't need C++ code to be defined, it can all work from QML alone.

class List : public QAbstractListModel {
    Q_OBJECT
    QList<QObject *> _data;

    Q_PROPERTY(int size READ size NOTIFY sizeChanged)
    Q_PROPERTY(QQmlListProperty<QObject> content READ content)
    Q_PROPERTY(QObject * parent READ parent WRITE setParent)
    Q_CLASSINFO("DefaultProperty", "content")
public:
    List(QObject *parent = 0) : QAbstractListModel(parent) { }
    int rowCount(const QModelIndex &p) const { Q_UNUSED(p) return _data.size(); }
    QVariant data(const QModelIndex &index, int role) const {
        Q_UNUSED(role)
        return QVariant::fromValue(_data[index.row()]);
    }
    QHash<int, QByteArray> roleNames() const {
        static QHash<int, QByteArray> roles = { { Qt::UserRole + 1, "object" } };
        return roles;
    }
    int size() const { return _data.size(); }
    QQmlListProperty<QObject> content() { return QQmlListProperty<QObject>(this, _data); }

public slots:
    void add(QObject * o) { insert(o, _data.size()); }

    void insert(QObject * o, int i) {
        if (i < 0 || i > _data.size()) i = _data.size();
        beginInsertRows(QModelIndex(), i, i);
        _data.insert(i, o);
        o->setParent(this);
        sizeChanged();
        endInsertRows();
    }

    QObject * take(int i) {
        if ((i > -1) && (i < _data.size())) {
            beginRemoveRows(QModelIndex(), i, i);
            QObject * o = _data.takeAt(i);
            o->setParent(0);
            sizeChanged();
            endRemoveRows();
            return o;
        } else qDebug() << "ERROR: take() failed - object out of bounds!";
        return 0;
    }

    QObject * get(int i) {
        if ((i > -1) && (i < _data.size())) return _data[i];
        else  qDebug() << "ERROR: get() failed - object out of bounds!";
        return 0;
    }

    void internalChange(QObject * o) { // added to force sort/filter reevaluation
      int i = _data.indexOf(o);
      if (i == -1) {
        qDebug() << "internal change failed, obj not found";
        return;
      } else {
        dataChanged(index(i), index(i));
      }
    }

signals:
    void sizeChanged();
};

然后,在你 qmlRegisterType("Core", 1, 0, "List"); 之后,你几乎可以以任何你想要的方式使用它 - 它会保存任何 QObject 或者派生的,自然包括QMLs QtObject 可以直接作为模型来驱动ListView.您可以使用插槽或声明式动态填充它,如下所示:

Then, after you qmlRegisterType<List>("Core", 1, 0, "List"); you can use it pretty much any way you want to - it will hold any QObject or derived, naturally including QMLs QtObject It can directly be used as a model to drive a ListView. You can populate it dynamically using the slots or declarative, like this:

List {
    QtObject { ... }
    QtObject { ... }
    List {
        QtObject { ... }
        QtObject { ... }
    }
}

它还将处理对象所有权,您可以轻松嵌套它,从本质上生成一个分区树模型 - 请注意,您不能使用 QML 的 ListModel 声明性地执行此操作.您可能想要添加一个 parentChanged 信号并实现一个发出它的 setter,如果您想绑定一个变化的父级,这在我的情况下是没有必要的.

It will also handle object ownership, and you can easily nest it, producing in essence a compartmentalized tree model - note that you can't declaratively do that with QML's ListModel. You may want to add a parentChanged signal and implement a setter that emits it if you want to bind against a changing parent, it was not necessary in my case.

至于如何在视图中使用它,您可以使用 objectName 属性或 int type 属性或基本上任何方法来区分不同的对象类型,并为委托使用 Loader:

As of how to use it with a view, you can either use the objectName property or an int type property or basically any means to discern between different object types, and use a Loader for the delegate:

Loader {
    // using component in order to capture context props and present to the variable delegate
    sourceComponent: Qt.createComponent(obj.objectName + ".qml")
    // if that is not needed simply use
    // source: obj.objectName + ".qml"
    // or setSource to pass specific properties to delegate properties
    // Component.onCompleted: setSource(obj.objectName + ".qml", {/*prop list*