У меня есть некоторый модельный класс, который наследует QAbstractListModel
:
VehiclesModel.h
:
class VehiclesModel : public QAbstractListModel {
Q_OBJECT
public:
enum Roles {
ImagePathRole = Qt::UserRole + 1, // QString
NameRole // QString
};
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const override { ... }
virtual QVariant data(const QModelIndex & index, int role) const override { ... }
virtual QHash<int, QByteArray> roleNames() const override {
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles[ImagePathRole] = "imagePath";
roles[NameRole] = "name";
return roles;
}
};
main.cpp
:
#include "VehiclesModel.h"
int main(int argc, char * argv[]) {
QGuiApplication app(argc, argv);
VehiclesModel vehiclesModel;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("vehiclesModel", &vehiclesModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
А также ComboBox
который отображает эту модель:
main.qml
:
ComboBox {
id: control
model: vehiclesModel
delegate: ItemDelegate {
contentItem: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
highlighted: control.highlightedIndex == index
}
contentItem: RowLayout {
Image {
source: ??imagePath??
}
Label {
text: ??name??
}
}
}
Я хочу настроить ComboBox
показать изображение и название автомобиля. Я могу получить доступ к данным модели из ItemDelegate
но как получить доступ к данным модели за пределами ItemDelegate
? Например, я хочу получить доступ к текущим данным индекса (ImagePathRole
а также NameRole
) для отображения изображения и имени автомобиля в contentItem
,
Можно ли это сделать без звонка QAbstractListModel
методы напрямую (т.е. index()
а также data()
методы) и их изготовление Q_INVOKABLE
?
В настоящее время это не совсем приличный встроенный способ, к сожалению, этого мне недоставало, и я подумал о том, чтобы реализовать что-то для этого в функциональных возможностях моделей QML, но я еще не было времени, чтобы сделать это.
В настоящее время вы можете сделать это самостоятельно (как вы обсуждаете), за счет безопасности типов и т. Д., Или (как я обычно говорил ранее) вы можете создать подкласс QObject для представлять отдельный элемент в модели (ItemDataThing или как вы его называете); предоставить ему исходную модель & индекс, свойства и пусть он представляет один экземпляр данных из модели.
Что-то вроде:
class ImageDataThing : public QObject
{
Q_OBJECT
Q_PROPERTY(QString imagePath READ imagePath NOTIFY imagePathChanged)
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
public:
QString imagePath() const;
QAbstractItemModel *model() const;
void setModel(const QAbstractItemModel *newModel);
int index() const;
void setIndex(int newIndex);
signals:
void imagePathChanged(const QString &imagePath);
void modelChanged(QAbstractItemModel *model);
void indexChanged(int indexChanged);
};
… и в вашей реализации всякий раз, когда модель установлена, подключите сигналы изменения (например, rowInserted, rowRemoved, …), чтобы изменить сохраненный индекс (если имеется), чтобы он отображался в правильном месте в модели.
В средствах получения данных модели (здесь, например, imagePath), получите доступ к экземпляру модели (используя индекс), чтобы получить данные и вернуть их.
Это имеет очевидный недостаток, заключающийся в том, что он состоит из множества шаблонов, но, с другой стороны, это простой в написании код, если вы знакомы с моделями, безопасны по типу, и его можно довольно легко автоматически сгенерировать.
Вы можете создать свою собственную функцию для получения данных из модели, например ту, которую я сейчас использую,
VehiclesModel.h
:
public slots:
int size() const; // to access from QML javascript
QVariant getData(int index, int role); // to access from QML javascript
VehiclesModel.cpp
:
int VehiclesModel::size() const {
return m_list.size();
}
QVariant VehiclesModel::getData(int index, int role) {
if (index < 0 || index >= m_list.count())
return QVariant();
switch (role) {
case ImagePathRole:
return ...
break;
default:
break;
}
}
Я настоятельно рекомендую посмотреть на Qt QML Tricks Библиотека, созданная Томасом Бутру:
http://gitlab.unique-conception.org/qt-qml-tricks/
Более конкретно QQmlObjectListModel
(от Qt QML Models
) может сделать трюк для вас.
Расширение с использованием Qt Super-Macros
, это уменьшает накладные расходы на запись сеттеров / геттеров!
Эти макросы в основном расширяются до Q_PROPERTY, что приводит к доступности из QML и добавляет определение установщика, получателя и закрытой переменной.
Использование в вашем конкретном случае это может выглядеть примерно так, быстро записывается, не проверяется (проверьте, используя правильный индекс для ссылки на модель):
VehicleItem.h
:
#include <QObject>
#include "QQmlVarPropertyHelpers.h" // Include library Qt Super-Macros
class VehicleItem : public QObject {
Q_OBJECT
QML_WRITABLE_VAR_PROPERTY(QString, imagePath)
QML_WRITABLE_VAR_PROPERTY(QString, name)
public:
explicit VehicleItem(QString imagePath, QString name, QObject* parent=0)
: QObject (parent)
, m_imagePath (imagePath)
, m_name (name)
{}
};
VehiclesModel.h
:
#include <QObject>
#include "QQmlObjectListModel.h" // Include library Qt QML Models
#include "VehicleItem.h"
class VehiclesModel : public QObject {
Q_OBJECT
QML_OBJMODEL_PROPERTY(VehicleItem, modelList)
public:
explicit VehiclesModel(QObject *parent = 0);
};
VehiclesModel.c
:
#include "VehiclesModel.h"
VehiclesModel::VehiclesModel(QObject *parent) :
QObject(parent), m_modelList(new QQmlObjectListModel<VehicleItem>())
{}
main.c (remains the same)
:
#include "VehiclesModel.h"
int main(int argc, char * argv[]) {
QGuiApplication app(argc, argv);
VehiclesModel vehiclesModel;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("vehiclesModel", &vehiclesModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
:
ComboBox {
id: control
model: vehiclesModel
delegate: ItemDelegate {
contentItem: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
highlighted: control.highlightedIndex == index
}
contentItem: RowLayout {
Image {
source: vehiclesModel.modelList.get(index).imagePath
}
Label {
text: vehiclesModel.modelList.get(index).name
}
}
}
Поскольку modelList (а также imagePath и name) расширяется макросом до Q_PROPERTY, он доступен со стороны QML.
Чтобы узнать все подробности об этой библиотеке, обязательно посмотрите молниеносный доклад Томаса Бутру на QtWS2015: https://www.youtube.com/watch?v=96XAaH97XYo
Бесстыдная вилка для моего Библиотека SortFilterProxyModel.
Проблема, которую вы задаете, на самом деле является головокружительным сценарием. Я нашел способ сделать это несколько правильно, но это довольно сложно и включает в себя внешнюю библиотеку. В моем решении мы фильтруем исходную модель, чтобы выставить только элемент, соответствующий текущему индексу комбинированного списка, и создаем экземпляр делегата для этого элемента и используем его как contentItem
из ComboBox
,
Преимущество этого состоит в том, что вам не нужно изменять вашу модель и синхронизировать ее с изменениями модели.
import SortFilterProxyModel 0.2 // from https://github.com/oKcerG/SortFilterProxyModel
import QtQml 2.2
/*
...
*/
ComboBox {
id: control
model: vehiclesModel
delegate: ItemDelegate {
contentItem: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
highlighted: control.highlightedIndex == index
}
contentItem: { currentIndex; return selectedInstantiator.object; } // use currentIndex to force the binding reevaluation. When the model changes, the instantiator doesn't notify object has changed
Instantiator {
id: selectedInstantiator
model: SortFilterProxyModel {
sourceModel: control.model
filters: IndexFilter {
minimumIndex: control.currentIndex
maximumIndex: control.currentIndex
}
}
delegate: RowLayout {
Image {
source: imagePath
}
Label {
text: name
}
}
}
}