Как создать универсальную объектную модель для использования в QML?
Я хотел бы знать, есть ли какой-либо макрос или способ, как зарегистрировать модель Qt как свойство QObject.
Например, у меня есть AnimalModel
( http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html).
Я знаю, что могу передать его в корневой контекст 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, визуализирующим тот тип объекта, который он создает. Таким образом, у вас есть как разные объекты в модели, так и разные элементы 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
или производные, естественно, включая QML 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, вы можете создать массив Component
s и использовать соответствующий индекс в качестве исходного компонента. Вы можете либо выставить object
как собственность Loader
и иметь фактический объектный интерфейс ссылки на него parent.object.prop
или вы можете использовать setSource(name + ".qml", {"object": object})
и иметь свойство объекта непосредственно в этот элемент, однако setSource
будет работать только с внешними источниками, а не со встроенными Component
s. Обратите внимание, что в случае внешнего источника, object
будет доступен даже без каких-либо действий для его пересылки, однако по какой-то причине он не работает со встроенными компонентами, и для таких компонентов единственный возможный способ - представить его как свойство загрузчика.