QML数据模型(Model)
QML中的ListView,GridView和Repeater等元素需要数据模型来提供要显示的数据.这些元素需要一个为模型中的每一项数据生成一个实例的代理组件(delegate component).模型可以是静态的,也可对其动态修改,插入,删除,移动.
给代理提供的数据通过叫做角色的数据绑定到代理.下面的ListModel有两个角色,type和age,ListView带有一个代理,并绑定这些角色以显示他们的值:
import QtQuick 1.0
Item {
width: 200; height: 250
ListModel {
id: myModel
ListElement { type: "Dog"; age: 8 }
ListElement { type: "Cat"; age: 5 }
}
Component {
id: myDelegate
Text { text: type + ", " + age }
}
ListView {
anchors.fill: parent
model: myModel
delegate: myDelegate
}
}
如果模型属性和代理的属性有名称冲突,可以在角色名称前加上模型名称.例如,如果Text元素也有type或age属性,上面的text元素会显示这些属性值,而不是模型中的项目.这时,可以使用model.age和model.type,确保代理显示模型中的数据项.
模型中的项目索引角色也可用在代理中.数据项从模型中删除后这个索引值被设置为-1.如果绑定这个索引角色,需要注意这个逻辑上可能的索引值-1,表示这个数据项还不可用.(通常这个数据项很快就会被删除,也可能通过在某些视图设置delayRemove扩展属性,由延时代理销毁.)
对于没有角色名称的数据模型(如下面的QStringList)可以使用modelData角色来引用数据.只有一个角色的模型也可使用modelData角色.这时modelData角色包含的数据与命名的角色相同.
QML提供了几个内建的数据模型元素.此外,可在C++中创建模型,用于QML元素.
视图引用模型中的数据并显示.QML使用Positioner和Repeater项来定位排列模型中的数据项.
QML数据模型ListModel
ListModel是QML中简单的层次元素.其中的角色由ListElement属性指定.
ListModel {
id: fruitModel
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
上面的模型有两个角色--name和cost.他们可以绑定到ListView的代理,如:
ListView {
anchors.fill: parent
model: fruitModel
delegate: Row {
Text { text: "Fruit: " + name }
Text { text: "Cost: $" + cost }
}
}
ListModel提供了可直接在JavaScript中调用的方法.这时,第一次插入的项决定使用模型的视图可用的角色.例如,如果创建了一个空ListModel,并在JavaScript中填充,第一次插入时指定的角色将显示在视图中:
ListModel { id: fruitModel }
...
MouseArea {
anchors.fill: parent
onClicked: fruitModel.append({"cost": 5.95, "name":"Pizza"})
}
当点击MouseArea时,fruitModel中会产生两个角色,cost和name.后面增加其他角色,使用模型的视图也只能使用这两个.调用ListModel::clear()重置模型中的角色.
XmlListModel
XmlListModel用来从XML数据源构造模型.角色由XmlRole元素指定.
下面的模型有三个角色:title,link和description:
XmlListModel {
id: feedModel
source: "http://rss.news.yahoo.com/rss/oceania"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
XmlRole { name: "link"; query: "link/string()" }
XmlRole { name: "description"; query: "description/string()" }
}
RSS范例展示了如何使用XmlListModel显示RSS源.
VisualItemModel
VisualItemModel允许QML元素作为模型.
这个模型中包含数据和代理;VisualItemModel的子项提供了代理的内容.这个模型不提供任何角色.
VisualItemModel {
id: itemModel
Rectangle { height: 30; width: 80; color: "red" }
Rectangle { height: 30; width: 80; color: "green" }
Rectangle { height: 30; width: 80; color: "blue" }
}
ListView {
anchors.fill: parent
model: itemModel
}
注意上例中不需要代理.模型中的数据项提供了可视化元素,将显示到视图中..
C++数据模型
C++中定义的模型可用于QML.可向QML元素暴露C++中已存的数据模型或复杂的数据集.
C++中可使用QStringList, QList<QObject*> 或QAbstractItemModel类定义模型.前面两个可用于暴露简单的数据集,QAbstractItemModel则可定义更复杂的模型.
基于QStringList的模型
可定义简单的QStringList模型,使用modalData角色访问其中的内容.
下面的ListView中有一个代理使用modelData角色使用模型中的数据项:
ListView {
width: 100; height: 100
anchors.fill: parent
model: myModel
delegate: Rectangle {
height: 25
width: 100
Text { text: modelData }
}
}
Qt应用程序可以加载这个QML文档并设置一个叫做myModel的QStringList类型的值:
QStringList dataList;
dataList.append("Item 1");
dataList.append("Item 2");
dataList.append("Item 3");
dataList.append("Item 4");
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));
完整范例见Qt的范例:examples/declarative/modelviews/stringlistmodel目录.
注意:视图无法知道QStringList中的内容变化.如果QStringList发生变化,需要再次调用QDeclarativeContext::setContextProperty()重置模型.
基于QObjectList的模型
QObject*列表也可作为模型.QList<QObject*>提供了一个对象列表的属性.
下面的应用程序创建了一个DataObject对象,使用Q_PROPERTY声明的命名属性,当QList<DataObject>暴露给QML时,可按属性名称作为角色来访问:
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)
...
};
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QList<QObject*> dataList;
dataList.append(new DataObject("Item 1", "red"));
dataList.append(new DataObject("Item 2", "green"));
dataList.append(new DataObject("Item 3", "blue"));
dataList.append(new DataObject("Item 4", "yellow"));
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", QVariant::fromValue(dataList));
...
QObject*可作为modelData的属性来访问.为方便,对象的属性也可直接用于代理上下文.这里,view.qml在ListView代理中引用了数据模型属性:
ListView {
width: 100; height: 100
anchors.fill: parent
model: myModel
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color
Text { text: name }
}
}
注意使用color属性时使用了全名称标识.QML对象的属性不能与模型对象属性同名,可使用modelData对象来访问数据项.
完整范例见examples/declarative/modelviews/objectlistmodel.
注意:无法知道QList中的内容发生了变化.如果QList变化了,有必要调用QDeclarativeContext::setContextProperty()重置模型.
QAbstractItemModel
可使用QAbstractItemModel的子类定义模型.如果有一个复杂的模型无法用其他方式实现,就可以使用这种方式.QAbstractItemModel在发生变化时会自动通知QML视图.
QAbstractItemModel子类向QML暴露的角色可使用QAbstractItemModel::setRoleNames()设置.默认的角色名称由Qt设置:
Qt Role QML角色名称
Qt::DisplayRole 显示
Qt::DecorationRole 修饰
下面的应用程序有一个QAbstractItemModel子类模型叫做AnialModel,具有type和size角色.为在QML中访问这些属性调用了QAbstractItemModel::setRoleNames():
class Animal
{
public:
Animal(const QString &type, const QString &size);
...
};
class AnimalModel : public QAbstractListModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
...
};
AnimalModel::AnimalModel(QObject *parent)
: QAbstractListModel(parent)
{
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
setRoleNames(roles);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal("Wolf", "Medium"));
model.addAnimal(Animal("Polar bear", "Large"));
model.addAnimal(Animal("Quoll", "Small"));
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", &model);
...
这个模型显示在ListView中,代理可访问type和size角色:
ListView {
width: 200; height: 250
anchors.fill: parent
model: myModel
delegate: Text { text: "Animal: " + type + ", " + size }
}
模型更改后QML视图自动更新.记住模型更改必须遵守标准规则,当模型修改后必须调用QAbstractItemModel::dataChanged(),QAbstractItemModel::beginInsertRows()等等,通知视图模型被修改了.更多信息见模型子类的参考.
完整范例见examples/declarative/modelviews/abstractitemmodel 目录.
QAbstractItemModel表现为一个层次表,但现在QML只提供了可显示列表数据的视图.为了显示模型层次的子列表,VisualDataModel元素提供了几个属性和方法与QAbstractItemModel类型的模型共用:
hasModelChildren 确定节点是否有子节点.
VisualDataModel::rootIndex 指定跟节点
VisualDataModel::modelIndex() 返回可给VisualDataModel::rootIndex赋值的QModelIndex类型值
VisualDataModel::parentModelIndex() 返回可给VisualDataModel::rootIndex赋值的QModelIndex类型值
向QML暴露C++数据模型
上例中使用QDeclarativeContext::setContextProperty()设置可用于QML组件的模型.另一种方式是在C++插件中注册一个C++模型类作为QML类型.这样可以直接在QML中声明模型元素:
class MyModelPlugin : public QDeclarativeExtensionPlugin
{
public:
void registerTypes(const char *uri)
{
qmlRegisterType<MyModel>(uri, 1, 0,
"MyModel");
}
}
Q_EXPORT_PLUGIN2(mymodelplugin, MyModelPlugin);
MyModel {
id: myModel
ListElement { someProperty: "some value" }
}
ListView {
width: 200; height: 250
model: myModel
delegate: Text { text: someProperty }
}
写C++插件的更多信息见Writing QML extensions with C++.
其他数据模型--整形
一个整数可指示模型包含的元素个数.这时,模型没有任何数据角色.
下面范例中创建一个有五个元素的ListView:
Item {
width: 200; height: 250
Component {
id: itemDelegate
Text { text: "I am item number: " + index }
}
ListView {
anchors.fill: parent
model: 5
delegate: itemDelegate
}
}
对象实例
一个对象实例可指定为模型.对象属性可作为角色.
下例创建一个有一个项的列表,展示Text.text的颜色.注意这里使用了全命名model.color避免命名冲突.
Rectangle {
width: 200; height: 250
Text {
id: myText
text: "Hello"
color: "#dd44ee"
}
Component {
id: myDelegate
Text { text: model.color }
}
ListView {
anchors.fill: parent
anchors.topMargin: 30
model: myText
delegate: myDelegate
}
}
在代理中访问视图和模型
可在代理中访问其所在的视图,在ListView的代理中使用ListView.view属性,或GridView中使用GridVie.view等.而且可使用ListView.view.model来访问模型的属性.
这对于在多个视图时使用同一个代理很有用,例如,想为每个视图创建不同的修饰部分或其他特性,需要针对每个视图设置不同的属性.同样,可能需要访问或显示模型的属性.
下面例子中,代理显示模型的language属性,一个字段的颜色依赖于fruit_color属性.
Rectangle {
width: 200; height: 200
ListModel {
id: fruitModel
property string language: "en"
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
Component {
id: fruitDelegate
Row {
Text { text: " Fruit: " + name; color: ListView.view.fruit_color }
Text { text: " Cost: $" + cost }
Text { text: " Language: " + ListView.view.model.language }
}
}
ListView {
property color fruit_color: "green"
model: fruitModel
delegate: fruitDelegate
anchors.fill: parent
}
}
其他重要的情形是当有些情况下(如鼠标点击)代理需要更新模型中的数据.这时可以在模型中定义一个函数,如:
setData(int row, const QString & field_name, QVariant new_value),
...在代理中调用这个函数:
ListView.view.model.setData(index, field, value)
...假设field保存了需要更新的字段名称,value中保存了新值.