在委托中,您可以使用role.property
约定来引用项目。默认角色是display
. 当然,必须Person
从 派生QObject
,并且必须在 QML 引擎中注册。
下面的代码演示了如何:
创建一个明智的行为ObjectListModel
来存储QObjects
, 可从 QML 使用。
创建一个QObject
保留数据的派生类。
从委托上显示的弹出菜单访问数据对象的属性和可调用方法。
该模型可以设置为自动通知包含的 QObjects 的属性的更改。此类通知,如果是由批量更改(比如在循环中完成)导致的,则会合并并作为单个dataChanged
事件发送。
不幸的是, a 的 user 属性QObject
没有特殊含义——您仍然需要使用.property
选择器来访问它。
可以直接观察到模型的正确行为,因为有两个列表与同一个模型挂钩——它们最好显示相同的东西。
ObjectListModel
还可以实现角色和属性之间的映射。目前,显示和编辑角色都选择整个对象,而不是它的任何特定属性。
如果QObject
s 的存储开销太大,另一种模型实现可以动态创建QObject
POD 类型的适配器。
主文件
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QAbstractListModel>
#include <QQmlContext>
#include <QtQml>
#include <QSet>
#include <QBasicTimer>
#include <functional>
class Person : public QObject {
Q_OBJECT
Q_PROPERTY(QString name NOTIFY nameChanged MEMBER m_name)
QString m_name;
public:
Q_INVOKABLE Person(QObject * parent = 0) : QObject(parent) { setRandomName(); }
Q_INVOKABLE Person(QString name, QObject * parent = 0) :
QObject(parent), m_name(name) {}
Q_SIGNAL void nameChanged(const QString &);
Q_INVOKABLE void setRandomName() {
static const QString names = "Badger,Shopkeeper,Pepperpots,Gumbys,Colonel";
static const QStringList nameList = names.split(',');
QString newName = nameList.at(qrand() % nameList.length());
if (newName != m_name) {
m_name = newName;
emit nameChanged(m_name);
}
}
};
class ObjectListModel : public QAbstractListModel {
Q_OBJECT
Q_DISABLE_COPY(ObjectListModel)
//! Whether changes to underlying objects are exposed via `dataChanged` signals
Q_PROPERTY(bool elementChangeTracking
READ elementChangeTracking WRITE setElementChangeTracking
NOTIFY elementChangeTrackingChanged)
QObjectList m_data;
std::function<QObject*()> m_factory;
bool m_tracking;
QBasicTimer m_notifyTimer;
QMap<int, char> m_notifyIndexes;
//! Updates the property tracking connections on given object.
void updateTracking(QObject* obj) {
const int nIndex = metaObject()->indexOfSlot("propertyNotification()");
QMetaMethod const nSlot = metaObject()->method(nIndex);
const int props = obj->metaObject()->propertyCount();
if (m_tracking) for (int i = 0; i < props; ++i) {
const QMetaProperty prop = obj->metaObject()->property(i);
if (prop.hasNotifySignal()) connect(obj, prop.notifySignal(), this, nSlot);
} else {
disconnect(obj, 0, this, 0);
}
}
//! Receives property notification changes
Q_SLOT void propertyNotification() {
int i = m_data.indexOf(sender());
if (i >= 0) m_notifyIndexes.insert(i, 0);
// All of the notifications will be sent as a single signal from the event loop.
if (!m_notifyTimer.isActive()) m_notifyTimer.start(0, this);
}
protected:
//! Emits the notifications of changes done on the underlying QObject properties
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_notifyTimer.timerId()) return;
emit dataChanged(index(m_notifyIndexes.begin().key()),
index((m_notifyIndexes.end()-1).key()),
QVector<int>(1, Qt::DisplayRole));
m_notifyTimer.stop();
m_notifyIndexes.clear();
}
public:
//! A model that creates instances via a given metaobject
ObjectListModel(const QMetaObject * mo, QObject * parent = 0) :
QAbstractListModel(parent),
m_factory([mo, this](){
return mo->newInstance(Q_ARG(QObject*, this));
}),
m_tracking(false)
{}
//! A model that creates instances using a factory function
ObjectListModel(const std::function<QObject*()> & factory,
QObject * parent = 0) :
QAbstractListModel(parent), m_factory(factory), m_tracking(false)
{}
~ObjectListModel() {
qDeleteAll(m_data);
}
bool elementChangeTracking() const { return m_tracking; }
void setElementChangeTracking(bool tracking) {
if (m_tracking == tracking) return;
for (QObject* obj : m_data) updateTracking(obj);
emit elementChangeTrackingChanged(m_tracking = tracking);
}
Q_SIGNAL void elementChangeTrackingChanged(bool);
int rowCount(const QModelIndex &) const Q_DECL_OVERRIDE {
return m_data.count();
}
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE {
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return QVariant::fromValue(m_data.at(index.row()));
}
return QVariant();
}
bool setData(const QModelIndex &index, const QVariant &value, int role)
Q_DECL_OVERRIDE {
Q_UNUSED(role);
QObject* object = value.value<QObject*>();
if (!object) return false;
if (object == m_data.at(index.row())) return true;
delete m_data.at(index.row());
m_data[index.row()] = object;
emit dataChanged(index, index, QVector<int>(1, role));
return true;
}
Q_INVOKABLE bool insertRows(int row, int count,
const QModelIndex &parent = QModelIndex())
Q_DECL_OVERRIDE {
Q_UNUSED(parent);
beginInsertRows(QModelIndex(), row, row + count - 1);
for (int i = row; i < row + count; ++ i) {
QObject * object = m_factory();
Q_ASSERT(object);
m_data.insert(i, object);
updateTracking(object);
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
}
endInsertRows();
return true;
}
Q_INVOKABLE bool removeRows(int row, int count,
const QModelIndex &parent = QModelIndex())
Q_DECL_OVERRIDE {
Q_UNUSED(parent);
beginRemoveRows(QModelIndex(), row, row + count - 1);
while (count--) delete m_data.takeAt(row);
endRemoveRows();
return true;
}
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<Person>();
ObjectListModel model1(&Person::staticMetaObject);
model1.setElementChangeTracking(true);
model1.insertRows(0, 1);
engine.rootContext()->setContextProperty("model1", &model1);
engine.load(QUrl("qrc:/main.qml"));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
window->show();
return app.exec();
}
#include "main.moc"
主.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
main.qml
import QtQuick 2.0
import QtQml.Models 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300; height: 300
Row {
width: parent.width
anchors.top: parent.top
anchors.bottom: row2.top
Component {
id: commonDelegate
Rectangle {
width: view.width
implicitHeight: editor.implicitHeight + 10
border.color: "red"
border.width: 2
radius: 5
TextInput {
id: editor
anchors.margins: 1.5 * parent.border.width
anchors.fill: parent
text: edit.name // "edit" role of the model, to break the binding loop
onTextChanged: {
display.name = text; // set the name property of the data object
}
}
Menu {
id: myContextMenu
MenuItem { text: "Randomize"; onTriggered: display.setRandomName() }
MenuItem { text: "Remove"; onTriggered: model1.removeRows(index, 1) }
}
MouseArea {
id: longPressArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: myContextMenu.popup()
}
}
}
spacing: 2
ListView {
id: view
width: (parent.width - parent.spacing)/2
height: parent.height
model: DelegateModel {
id: delegateModel1
model: model1
delegate: commonDelegate
}
spacing: 2
}
ListView {
width: (parent.width - parent.spacing)/2
height: parent.height
model: DelegateModel {
model: model1
delegate: commonDelegate
}
spacing: 2
}
}
Row {
id: row2
anchors.bottom: parent.bottom
Button {
text: "Add Page";
onClicked: model1.insertRows(delegateModel1.count, 1)
}
}
}