Qt中的Model-View编程框架,对Controller部分做了改动,引入了Delegate的概念,合起来就是Model-View-Delegate,Model负责提供数据,View负责提供布局管理和Item创建,样式由Delegate负责。
ListView
ListView用来显示一个条目列表,条目对应的数据来自于Model,而每个条目的外观则有Delegate决定。要使用ListView,必须为其指定一个Model,一个Delegate。Model可以是QML内建类型,如ListModel、XmlListModel,也可以是C++中实现的QAbstractItemModel或QAbstractListModel的派生类。
import QtQuick 2.0
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
Rectangle {
width: 360;
height: 300;
color: "#EEEEEE";
Component {
id: phoneDelegate;
Item {
id: wrapper;
width: parent.width;
height: 30;
MouseArea {
anchors.fill: parent;
onClicked: wrapper.ListView.view.currentIndex = index;
}
RowLayout {
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
spacing: 8;
Text {
id: coll;
text: name;
color: wrapper.ListView.isCurrentItem ? "red" : "black";
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18;
Layout.preferredWidth: 120;
}
Text {
text: cost;
color: wrapper.ListView.isCurrentItem ? "red" : "black";
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18;
Layout.preferredWidth: 80;
}
Text {
text: manufacturer;
color: wrapper.ListView.isCurrentItem ? "red" : "black";
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18;
Layout.fillWidth: true;
}
}
}
}
ListView {
id: listView;
anchors.fill: parent;
delegate: phoneDelegate;
model: ListModel{
id: phoneModel;
ListElement {
name: "IPhone 3GS";
cost: "1000";
manufacturer: "Apple";
}
ListElement {
name: "IPhone 4";
cost: "1800";
manufacturer: "Apple";
}
ListElement {
name: "IPhone 4s";
cost: "2300";
manufacturer: "Apple";
}
ListElement {
name: "IPhone 5";
cost: "4900";
manufacturer: "Apple";
}
ListElement {
name: "xiao mi 2s";
cost: "1900";
manufacturer: "XiaoMi";
}
ListElement {
name: "OnePlus 3T";
cost: "2700";
manufacturer: "OnePlus";
}
ListElement {
name: "IQOO";
cost: "2100";
manufacturer: "VIVO";
}
}
focus: true;
highlight: Rectangle {
color: "lightblue";
}
}
}
在ListView对象中,使用ListModel对象充当ListView的model,在ListModel中,ListElement 代表一条数据。ListElement中的每一条数据都称为role,每一条数据都由role-name:role-value构成,role-name必须以小写字母开头,role-value必须时简单的常量,例如字符串、布尔值、数字或者枚举类型。在ListElement中定义的role,可以在Delegate中通过role-name来访问。
ListView的delegate属性类型为Component,id为phoneDelegate,phoneDelegate的顶层元素是Row,Row内嵌三个Text对象来展示Model定义的ListElementd的三个role。ListView暴露给delegate一个index属性,代表当前delegate实例对应的Item的索引位置。
示例中选中条目有一个浅蓝色背景,它由ListView的highlight属性初始化一个Rectangle来定义高亮北京
header
通过为ListView的header属性设置一个Component,ListView就可以显示自定义的表头
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
width: 360;
height: 300;
color: "#EEEEEE";
Component {
id: phoneModel;
ListModel {
ListElement {
name: "IPhone 3GS";
cost: "1000";
manufacturer: "Apple";
}
ListElement {
name: "IPhone 4";
cost: "1800";
manufacturer: "Apple";
}
ListElement {
name: "IPhone 4s";
cost: "2300";
manufacturer: "Apple";
}
ListElement {
name: "IPhone 5";
cost: "4900";
manufacturer: "Apple";
}
ListElement {
name: "xiao mi 2s";
cost: "1900";
manufacturer: "XiaoMi";
}
ListElement {
name: "OnePlus 3T";
cost: "2700";
manufacturer: "OnePlus";
}
ListElement {
name: "IQOO";
cost: "2100";
manufacturer: "VIVO";
}
}
}
Component {
id: headerView;
Item {
width: parent.width;
height: 30;
RowLayout {
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
spacing: 8;
Text {
text: "Name";
font.bold: true;
font.pixelSize: 20;
Layout.preferredWidth: 120;
}
Text {
text: "Cost";
font.bold: true;
font.pixelSize: 20;
Layout.preferredWidth: 80;
}
Text {
text: "Manufacturer";
font.bold: true;
font.pixelSize: 20;
Layout.fillWidth: true;
}
}
}
}
Component {
id: phoneDelegate;
Item {
id: wrapper;
width: parent.width;
height: 30;
MouseArea {
anchors.fill: parent;
onClicked: wrapper.ListView.view.currentIndex = index;
}
RowLayout {
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
spacing: 8;
Text {
id: coll;
text: name;
color: wrapper.ListView.isCurrentItem ? "red" : "black";
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18;
Layout.preferredWidth: 120;
}
Text {
text: cost;
color: wrapper.ListView.isCurrentItem ? "red" : "black";
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18;
Layout.preferredWidth: 80;
}
Text {
text: manufacturer;
color: wrapper.ListView.isCurrentItem ? "red" : "black";
font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18;
Layout.fillWidth: true;
}
}
}
}
ListView {
id: listView;
anchors.fill: parent;
delegate: phoneDelegate;
model: phoneModel.createObject(listView);
header: headerView;
focus: true;
highlight: Rectangle {
color: "lightblue"
}
}
}
ListView的delegate、model、header三个属性均由Component构成
footer
footer属性允许我们指定ListView的页脚,footerItem保存了footer组件常见出来的Item对象,这个Item会被添加到ListView的末尾,在所有可见的Item之后。
Component {
id: footerView;
Text {
width: parent.width;
font.italic: true;
color: "blue";
height: 30;
verticalAlignment: Text.AlignVCenter;
}
}
ListView {
id: listView;
anchors.fill: parent;
delegate: phoneDelegate;
model: phoneModel.createObject(listView);
header: headerView;
footer: footerView
focus: true;
highlight: Rectangle {
color: "lightblue"
}
onCurrentIndexChanged: {
if(listView.currentIndex >= 0){
var data = listView.model.get(listView.currentIndex);
listView.footerItem.text = data.name +" , " + data.cost + " , " + data.manufacturer;
}else {
listView.footerItem.text = " ";
}
}
}
onCurrentIndexChanged信号没有参数,只在当前条目选择变化时触发。
访问与修改Model
ListModel的count属性表示Model中有多少条数据,dynamicRoles属性为true时表示Model中的role对应的值可以修改,默认为false,不过要注意的是,一旦你使用了dynamicRoles,ListModel的性能会大大下降,通常它带来的性能损失是使用静态类型的4-6倍。
(1)访问数据
ListModel的get()方法用来获取指定索引位置的数据,返回一个QML对象,然后可以用这个QML对象访问数据的role了:
var data = listView.model.get(listView.currentIndex);
listView.footerItem.text = data.name +" , " + data.cost + " , " + data.manufacturer;
(2)删除数据
如果你想删除一条或多条数据,可以使用ListModel的remove(int index,int count)方法。清空一个model直接调用clear()方法。
将上面的phoneDelegate修改一下:实现双击删除一条数据
MouseArea {
anchors.fill: parent;
onClicked: wrapper.ListView.view.currentIndex = index;
onDoubleClicked: {
wrapper.ListView.view.model.remove(index);
}
}
修改一下footer组件,添加一个清除按钮,用来清除所有数据:
Component {
id: footerView;
Item {
id: footerRootItem;
width: parent.width;
height: 30;
signal clean();
Text {
id: txt;
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
font.italic: true;
color: "blue";
verticalAlignment: Text.AlignVCenter;
}
Button {
anchors.right: parent.right;
anchors.verticalCenter: parent.verticalCenter;
text: "Clear";
onClicked: footerRootItem.clean();
}
}
}
给ListView添加Component.onCompleted附加信号处理器:
Component.onCompleted: {
listView.footerItem.clean.connect(listView.model.clear);
}
(3)修改数据
想要修改Model的数据,可以使用ListModel的setProperty(int index, string property, variant value)方法:
listView.model.setProperty(5, "cost", 18888);
如果想替换某一条数据,可以使用set(int index, jsobject dict)方法,我们经常用对象的字面量表示法构造一个对象传递给set()方法:
listView.model.set(0, {"name":"iqooneo", "cost":2099, "manufacturer": "vivo"});
(4)添加数据
向model的尾部添加数据,使用append()方法:
listView.model.append(
{
"name" : "xiaomi",
"cost" : "2699",
"manufacturer" : "xiaomi"
}
);