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.