Как создать универсальную объектную модель для использования в QML?

Я хотел бы знать, есть ли макрос или способ регистрации модели Qt как свойства QObject.

например, у меня есть AnimalModel (http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel).

Я знаю, что могу передать его в корневой контекст QuickView

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

если у меня есть QObject, зарегистрированный через макросы Qml, я могу передать этот объект для просмотра:

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

но что, если я хочу иметь QObject, который содержит модель любых данных?

например:

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.

(Я знаю, что есть QQmlListProperty но QQmlListProperty не поддерживает частичное обновление. Всегда необходимо перестроить все объекты Qml)

1 ответов


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

Да, вы не пробовали? Конечно, это не будет AnimalModel но AnimalModel *, но пока модель наследует QAbstractListModel - это все, что вам нужно. Вам даже не нужно NOTIFY часть, как изменения, внутренняя к модели будет автоматически отражена в любом случае. modelAnimalsChanged имеет смысл только тогда, когда вы заменяете всю модель другой моделью и, естественно, закрываете предупреждения QML об использовании свойства без сигнала уведомления. Более чистый способ сделать последнее, когда модель объект не изменяется, чтобы просто вернуть AnimalModel * из слота или Q_INVOKABLE.

если вы хотите поистине гибкую модель, то вы можете сделать одно которое хранит QObject *, затем из QML вы можете создавать произвольные объекты с произвольными свойствами и добавлять в модель. Тогда из модели у вас есть один object роль, которая возвращает объект, и вы можете запрашивать и использовать объект для получения свойств, которые он содержит. В то время как реализация "классической" модели списка будет определять модель с статическая, фиксированная схема, использование такого подхода позволяет иметь в модели" аморфные " объекты с различными свойствами.

естественно, это требует некоторой безопасности типа, например имеет property int type для каждого объекта в такой модели, и на его основе можно определить доступные свойства для объекта. Мой обычный подход-иметь Loader для делегата и передать объект в качестве источника данных в различные реализации QML UI, визуализирующие этот тип объекта, который он создать экземпляр. Таким образом, у вас есть как разные объекты в модели, так и разные элементы QML в качестве делегатов представления.

последний шаг к созданию окончательного списка/объекта модели" Джек всех профессий " - реализовать QQmlListProperty и Q_CLASSINFO("DefaultProperty", "container") для него, что позволяет вам динамически составлять список/модель или использовать декларативный синтаксис QML. Также обратите внимание, что с помощью этого решения вы можете добавлять или удалять из такой модели, даже удалять декларативно созданные объекты.

также, в зависимости от сценария использования может потребоваться либо qmlRegisterType() или qmlRegisterUncreatableType() для модели.

хорошо, на второй взгляд, похоже, что под "моделью любых данных" вы имели в виду не модели без схемы, а просто разные модели схемы. В этом случае, вместо возврата AnimalModel *, вы можете использовать QAbstractListModel * или даже QObject * - он будет работать в QML в любом случае, поскольку он использует динамизм через мета-систему. Но, во всяком случае, модели без схемы намного более мощные и гибкие, и им не нужен код C++ для определения, он может работать только с QML.

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> * pHash;
        if (!pHash) {
            pHash = new QHash<int, QByteArray>;
            (*pHash)[Qt::UserRole + 1] = "object";
        }
        return *pHash;
    }
    int size() const { return _data.size(); }
    QQmlListProperty<QObject> content() { return QQmlListProperty<QObject>(this, _data); }

public slots:
    void add(QObject * o) {
        int i = _data.size();
        beginInsertRows(QModelIndex(), i, i);
        _data.append(o);
        o->setParent(this);
        sizeChanged();
        endInsertRows();
    }

    void insert(QObject * o, int i) {
        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;
    }
signals:
    void sizeChanged();
};

после qmlRegisterType<List>("Core", 1, 0, "List"); вы можете использовать его в значительной степени так, как вы хотите - он будет держать любой QObject или производные, естественно, включая QMLs QtObject его можно сразу использовать как модель для того чтобы управлять ListView. Вы можете заполнить его динамически, используя слоты или декларативный, например:

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

оно также отрегулирует владение предмета, и вы можете легко гнездиться оно, производящ в сути разделенная модель дерева-обратите внимание, что вы не можете декларативно сделать это с помощью QML ListModel. Вы можете добавить parentChanged сигнал и реализовать сеттер, который излучает его, если вы хотите привязать к изменяющемуся родителю, это не было необходимо в моем случае.

как использовать его с видом, вы можете либо использовать objectName собственность или int type свойства, и использовать Loader для делегата:

Loader {
    width: childrenRect.width
    height: childrenRect.height
}

если вы используете имя объекта, вы делаете загрузчик создать name.qml файл, если вы используете int, вы можете создать массив Components и используйте соответствующий индекс в качестве исходного компонента. Вы можете либо разоблачить object в собственность Loader и имеют фактическую ссылку на объект UI parent.object.prop, или вы можете использовать setSource(name + ".qml", {"object": object}) и имеют свойство объекта непосредственно в этот элемент, однако setSource будет работать только с внешними источниками, а не с inline Components. Обратите внимание, что в случае внешнего источника,object будет доступен даже без делать что-либо, чтобы переслать его, однако по какой-то причине он не работает со встроенными компонентами, с такими компонентами единственный возможный способ-выставить его как свойство загрузчика.