¿Cómo crear un modelo de objeto genérico para usar en QML?

Me gustaría saber si hay alguna macro o forma de registrar el modelo Qt como propiedad de QObject.

Por ejemplo, tengo AnimalModel ( http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel ).

Sé que puedo pasarlo al contexto raíz de QuickView

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

En caso de que tenga QObject registrado a través de macros Qml, puedo pasar este objeto para verlo también:

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

Pero, ¿qué sucede si quiero tener QObject que contiene el modelo de cualquier dato?

Por ejemplo:

 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) }; 

Cada ejemplo que encontré hasta ahora muestra cómo pasar QAbstractListModel al contexto raíz. Pero ninguno cómo usarlo como propiedad QObject.

(Sé que hay QQmlListProperty pero QQmlListProperty no admite la actualización parcial. Siempre es necesario reconstruir todos los objetos Qml)

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

Sí lo es, ¿no lo intentaste? Por supuesto, no será un AnimalModel sino un AnimalModel * , pero mientras el modelo herede QAbstractListModel , eso es todo lo que necesita. Ni siquiera necesita la parte NOTIFY , ya que los cambios internos al modelo se reflejarán automáticamente de todos modos. modelAnimalsChanged solo tiene sentido cuando reemplazas todo el modelo con un modelo diferente y, naturalmente, para cerrar las advertencias de QML sobre el uso de una propiedad sin una señal de notificación. Una forma más limpia de hacer esto último cuando el objeto modelo no cambia es simplemente devolver un AnimalModel * desde una ranura o un Q_INVOKABLE .

Si desea un modelo verdaderamente flexible, puede crear uno que almacene QObject * , luego desde QML puede crear objetos arbitrarios con propiedades arbitrarias y agregar al modelo. Luego, desde el modelo tiene un único rol de object que devuelve el objeto, y puede consultar y usar el objeto para recuperar las propiedades que contiene. Mientras que una implementación del modelo de lista “clásica” definirá un modelo con un esquema estático fijo, el uso de este enfoque permite tener objetos “amorfos” en el modelo con diferentes propiedades.

Naturalmente, esto requiere algún tipo de seguridad, por ejemplo, tener un property int type de property int type para cada objeto en dicho modelo, y en base a él puede determinar las propiedades disponibles para el objeto. Mi enfoque habitual es tener un Loader para un delegado, y hacer que pase el objeto como fuente de datos a diferentes implementaciones de IU de QML visualizando ese tipo de objeto que crea. De esta forma, tiene objetos diferentes en el modelo y diferentes elementos QML como delegates de vista.

El último paso para hacer el último objeto de lista / modelo de “jack of all trades” es implementar QQmlListProperty y Q_CLASSINFO("DefaultProperty", "container") para ello, lo que le permite componer la lista / modelo dinámicamente o usar el declarativo de QML syntax. También tenga en cuenta que con esta solución, puede agregar o quitar de dicho modelo, incluso eliminar objetos instanciados declarativamente.

Además, dependiendo de su escenario de uso, puede que tenga qmlRegisterType() o qmlRegisterUncreatableType() para el modelo.

De acuerdo, en una segunda mirada, parece que por “modelo de cualquier dato” no se refirió a modelos sin esquema sino simplemente a diferentes modelos de esquema. En ese caso, en lugar de devolver un AnimalModel * , puede usar un QAbstractListModel * o incluso un QObject * ; de todos modos funcionará en QML, ya que emplea dinamismo a través del meta sistema. Pero, en cualquier caso, los modelos sin esquema son mucho más poderosos y flexibles, y no necesitan que se defina el código C ++, todo puede funcionar solo desde QML.

 class List : public QAbstractListModel { Q_OBJECT QList _data; Q_PROPERTY(int size READ size NOTIFY sizeChanged) Q_PROPERTY(QQmlListProperty 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 roleNames() const { static QHash * pHash; if (!pHash) { pHash = new QHash; (*pHash)[Qt::UserRole + 1] = "object"; } return *pHash; } int size() const { return _data.size(); } QQmlListProperty content() { return QQmlListProperty(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(); }; 

Luego, después de qmlRegisterType("Core", 1, 0, "List"); puede usarlo de la forma que desee: contendrá cualquier QObject o derivado, naturalmente incluyendo QML QtObject Puede usarse directamente como modelo para manejar un ListView . Puede poblarlo dinámicamente usando los espacios o declarativo, como este:

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

También manejará la propiedad del objeto, y usted puede anidarlo fácilmente, produciendo en esencia un modelo de árbol compartimentado: tenga en cuenta que no puede hacer eso de manera declarativa con el ListModel de QML. Es posible que desee agregar una señal parentChanged e implementar un setter que la emita si desea vincularse con un padre cambiante, no fue necesario en mi caso.

A partir de cómo usarlo con una vista, puede usar la propiedad objectName o una propiedad de int type , y usar un Loader para el delegado:

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

Si usa el nombre del objeto, haga que el cargador cree un archivo name.qml , si usa un int, puede crear una matriz de Component y usar el índice apropiado como componente fuente. Puede exponer el object como una propiedad del Loader y hacer que la interfaz de usuario del objeto real lo parent.object.prop referencia parent.object.prop , o puede usar setSource(name + ".qml", {"object": object}) y tener el propiedad del objeto directamente en ese elemento; sin embargo, setSource solo funcionará con fonts externas, no con Component línea. Tenga en cuenta que en el caso de una fuente externa, el object será accesible incluso sin hacer nada para reenviarlo, sin embargo, por algún motivo, no funciona con componentes en línea, con dichos componentes la única forma posible es exponerlo como una propiedad de el cargador