c++ qt generics model qtquick2

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



qt generics (1)

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 haya registrado QObject a través de macros Qml, también puedo pasar este objeto para verlo:

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

Todos los ejemplos que encontré hasta ahora muestran 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 del modelo se reflejarán automáticamente de todos modos. modelAnimalsChanged solo tiene sentido cuando reemplaza todo el modelo con un modelo diferente y, naturalmente, cierra 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 hacer uno que almacene QObject * , luego, desde QML, puede crear objetos arbitrarios con propiedades arbitrarias y agregarlos al modelo. Luego, desde el modelo, tiene un rol de object único que devuelve el objeto, y puede consultar y usar el objeto para recuperar las propiedades que posee. Mientras que una implementación de modelo de lista "clásica" definirá un modelo con un esquema fijo estático, el uso de este enfoque permite tener objetos "amorfos" en el modelo con diferentes propiedades.

Naturalmente, esto requiere cierto tipo de seguridad, por ejemplo, tener una property int type para cada objeto en dicho modelo, y en función de ello 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 QML UI que visualizan el tipo de objeto que instancia. De esta forma, tiene dos objetos diferentes en el modelo y diferentes elementos QML como delegados de vista.

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

Además, dependiendo de su escenario de uso, es posible que deba qmlRegisterType() o qmlRegisterUncreatableType() para el modelo.

OK, en una segunda mirada, parece que por "modelo de cualquier dato" no se refería a modelos sin esquema sino simplemente a modelos de esquema diferentes. 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 el dinamismo a través del meta sistema. Pero, en cualquier caso, los modelos sin esquema son mucho más potentes 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<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; } 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(); };

Luego, después de qmlRegisterType<List>("Core", 1, 0, "List"); puede usarlo de la forma que desee: contendrá cualquier QObject o derivado, incluidos naturalmente los QML QtObject Se puede usar directamente como modelo para conducir un ListView . Puede rellenarlo dinámicamente utilizando las ranuras o declarativas, de esta manera:

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

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

En cuanto a cómo usarlo con una vista, puede usar la propiedad objectName o una propiedad de int type o básicamente cualquier medio para discernir entre diferentes tipos de objetos y usar un Loader para el delegado:

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*/}) }

Actualizar:

Aquí también está la esencia de la implementación de un proxy de ordenamiento y filtrado simple e igual de dinámico y genérico para acompañar este modelo para una usabilidad mejorada.