11 Model/View
Delegate实际上可以看成是Item的一个模板
11.1 ListView
ListView用于显示一个条目列表,数据来自于Model,每个条目的外观来自于Delegate
要使用ListView必须指定一个Model、一个Delegate
Model可以是QML内建类型,如ListModel、XmlListModel,也可以是C++中实现的QAbstracItemModel或QAbstractListModel的派生类
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15Window {width: 560height: 300color: "#EEEEEE"visible: trueComponent {id: phoneModelListModel {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: "B199"cost: "1590"manufacturer: "HuaWei"}ListElement {name: "MI 2S"cost: "1999"manufacturer: "XiaoMi"}ListElement {name: "GALAXY S5"cost: "4699"manufacturer: "Samsung"}}}Component {id: headviewItem {width: parent.widthheight: 30RowLayout {anchors.verticalCenter: parent.verticalCenterText {text: "Name"font.bold: truefont.pixelSize: 20Layout.preferredWidth: 120}Text {text: "Cost"font.bold: truefont.pixelSize: 20Layout.preferredWidth: 80}Text {text: "Manufacturer"font.bold: truefont.pixelSize: 20Layout.fillWidth: true}}}}Component {id: footerViewItem {signal cleansignal addsignal insertsignal moveproperty alias text: txt.textwidth: listView.widthheight: 30Text {id: txtanchors.left: parent.leftcolor: "green"height: parent.heightverticalAlignment: Text.AlignVCenter}Button {id: clearAllanchors.right: parent.rightanchors.verticalCenter: parent.verticalCenterheight: parent.heighttext: "clear"onClicked: {clean()}}Button {id: addOneanchors.right: clearAll.leftanchors.rightMargin: 5anchors.verticalCenter: parent.verticalCenterheight: parent.heighttext: "Add"onClicked: {add()}}Button {id: insertOneanchors.right: addOne.leftanchors.rightMargin: 5anchors.verticalCenter: parent.verticalCenterheight: parent.heighttext: "Insert"onClicked: {insert()}}Button {id: moveDownanchors.right: insertOne.leftanchors.rightMargin: 5anchors.verticalCenter: parent.verticalCenterheight: parent.heighttext: "MoveDown"onClicked: {move()}}}}Component {id: phoneDelegateItem {id: wrapperwidth: listView.widthheight: 30MouseArea {anchors.fill: parentonClicked: wrapper.ListView.view.currentIndex = indexonDoubleClicked: wrapper.ListView.view.model.remove(index)}RowLayout {anchors.verticalCenter: parent.verticalCenterwidth: parent.widthText {id: col1text: namecolor: wrapper.ListView.isCurrentItem ? "red" : "black"font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18Layout.preferredWidth: 120}Text {text: costcolor: wrapper.ListView.isCurrentItem ? "red" : "black"font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18Layout.preferredWidth: 80}Text {text: manufacturercolor: wrapper.ListView.isCurrentItem ? "red" : "black"font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18Layout.fillWidth: true}}}}ListView {id: listViewanchors.fill: parentdelegate: phoneDelegateheader: headviewfooter: footerViewmodel: phoneModel.createObject(listView)focus: truehighlight: Rectangle {color: "lightblue"}add: Transition {ParallelAnimation {NumberAnimation {property: "opacity"from: 0to: 1.0duration: 1000}NumberAnimation {property: "y"from: 0duration: 1000}}}displaced: Transition {SpringAnimation {properties: "y"spring: 3damping: 0.1epsilon: 0.25}}remove: Transition {SequentialAnimation {NumberAnimation {properties: "y"to: 0duration: 600}NumberAnimation {properties: "opacity"to: 0duration: 400}}}move: Transition {NumberAnimation {properties: "y"duration: 300easing.type: Easing.InQuart}}populate: Transition {NumberAnimation {property: "opacity"from: 0to: 1.0duration: 3000}}Component.onCompleted: {footerItem.clean.connect(model.clear)footerItem.add.connect(addOne)footerItem.insert.connect(insertOne)footerItem.move.connect(moveDown)}onCurrentIndexChanged: {console.log("index:", currentIndex)if (currentIndex >= 0) {var data = listView.model.get(currentIndex)footerItem.text = "%1 -> %2 -> %3".arg(data.name).arg(data.cost).arg(data.manufacturer)} else {footerItem.text = ""}}function addOne() {console.log("add")model.append({"name": "max3","cost": "1799","manufacturer": "samsung"})}function insertOne() {console.log("insert")model.insert(Math.round(Math.random() * model.count), {"name": "HTC One E8","cost": "2499","manufacturer": "htc"})}function moveDown() {console.log("moveDown:", currentIndex)if (currentIndex + 1 < model.count) {model.move(currentIndex, currentIndex + 1, 1)}}}
}
- ListModel专门用于定义列表数据,内部维护一个
ListElement
的列表,一个ListElement
对象代表一个数据。 - 多个
role
构成一个ListElement
,role
包含一个名字和一个值,名字必须以小写字母开头,值必须是简单的常量(字符串、布尔值、数字、枚举值) - 在
ListElement
中定义的role
,可以在Delegate
中通过名称访问 Delegate
使用Row
管理Text
对象来展现role,Text
对象的text
属性对应于role
的名称- ListView的
delegate
属性类型是Component
,Component的顶层元素是Row,Row内嵌三个Text对象来展示Model定义的ListElement的三个role - ListView给delegate暴露一个
index
属性,代表当前delegate示例对应的Item的索引位置,必要时可通过它来访问数据 - ListView定义
delayRemove
、isCurrentItem
、nextSection
、previousSection
、section
、view
等附加属性,以及add
、remove
两个附加信号,可以在delegate中直接访问(只有delegate的顶层Item才能直接使用这些附加属性和信号,非顶层Item则需通过顶层Item的id来访问这些附加属性) - ListView的
highlight
属性可以指定一个Component对象,它的Z序小于delegate实例化出来的Item对象。highlightFollowsCurrentItem
属性指定高亮背景是否跟随当前条目,当前条目变动时,高亮背景经过一个平滑的动画效果进行过渡
11.1.1 ListModel访问数据
count
属性代表Model中有多少条数据
dynamicRoles
属性为true时,则Model中的roles对应值的类型可以动态改变,但是性能将会严重下降,默认false。要使用它必须在添加数据之前
get(int)
方法用于获取指定索引位置的数据,返回一个QML对象
var data = listView.model.get(listView.currentIndex);
listView.footerItem.text = data.name + ", " + data.cost + ", " + data.manufacturer;
11.1.2 ListModel删除数据
remove(int index, int count)
方法用于删除数据
- index指明删除数据的索引位置
- count指示要删除的数据条数,默认为1
11.1.3 ListModel修改数据
setProperty(int index, string property, variant value)
方法用于修改数据
listView.model.setProperty(5, "cost", 16999);
- index指明修改数据的索引位置
- property数据内role的名字
- value要修改成的值
set(int index, jsobject dict)
方法用于替换数据
listView.model.set(0, {"name": "Z5S mini", "cost": 1999, "manufacturer": "ZhongXing"});
- index指明要替换数据的索引位置
- dict要替换成的数据
11.1.4 ListModel添加数据
append(jsobject dict)
用于在末尾添加一条数据
insert(int index, jsobject value)
用于在指定位置添加一条数据
listView.model.append({"name" : "MX3","cost" : "1799","manufacturer": "MeiZu"}
)
11.1.5 ListModel移动数据
move(int indexSrc, int indexDst, int count)
用于将一条数据移动到指定的位置
- indexSrc要移动的数据索引
- indexDst移动到的目标位置索引
- count移动的条数
11.1.6 section列表分组
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15Window {width: 560height: 300color: "#EEEEEE"visible: trueComponent {id: phoneModelListModel {// Apple sectionListElement {name: "iPhone 5"cost: "4900"manufacturer: "Apple"}ListElement {name: "iPhone 3GS"cost: "1000"manufacturer: "Apple"}ListElement {name: "iPhone 4"cost: "1800"manufacturer: "Apple"}ListElement {name: "iPhone 4S"cost: "2300"manufacturer: "Apple"}//HuaWei sectionListElement {name: "B199"cost: "1590"manufacturer: "HuaWei"}// Sumsung sectionListElement {name: "GALAXY S4"cost: "3099"manufacturer: "Samsung"}ListElement {name: "C8816D"cost: "590"manufacturer: "HuaWei"}ListElement {name: "GALAXY S5"cost: "4699"manufacturer: "Samsung"}// XiaoMi sectionListElement {name: "MI 2S"cost: "1999"manufacturer: "XiaoMi"}ListElement {name: "MI 3"cost: "1999"manufacturer: "XiaoMi"}}}Component {id: phoneDelegateItem {id: wrapperwidth: parent.widthheight: 30ListView.onAdd: {console.log("count:", ListView.view.count)}MouseArea {anchors.fill: parentonClicked: wrapper.ListView.view.currentIndex = index}RowLayout {anchors.left: parent.leftanchors.verticalCenter: parent.verticalCenterspacing: 8Text {id: col1text: namecolor: wrapper.ListView.isCurrentItem ? "red" : "black"font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18Layout.preferredWidth: 120}Text {text: costcolor: wrapper.ListView.isCurrentItem ? "green" : "black"font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18Layout.preferredWidth: 80}Text {text: manufacturercolor: wrapper.ListView.isCurrentItem ? "red" : "black"font.pixelSize: wrapper.ListView.isCurrentItem ? 22 : 18Layout.fillWidth: true}}}}Component {id: sectionHeaderRectangle {width: parent.widthheight: childrenRect.heightcolor: "lightsteelblue"Text {text: sectionfont.bold: truefont.pixelSize: 20}}}ListView {id: listViewanchors.fill: parentdelegate: phoneDelegatemodel: phoneModel.createObject(listView)focus: truehighlight: Rectangle {color: "lightblue"}section.property: "manufacturer"section.criteria: ViewSection.FullStringsection.delegate: sectionHeader}
}
section.property
:分组的依据,对应于数据的role-name
section.criteria
:指定section.property
的判断条件
- ViewSection.FullString(默认,全串匹配,不区分大小写)
- ViewSection.Firstcharacter(首字母匹配,不区分大小写)
section.delegate
:设定一个Component决定如何显示每个section
section.labelPositioning
:决定当前或下一个section标签的显示策略
- ViewSection.InlineLabels,这是默认方式。分组标签嵌入到Item之间显示。
- ViewSection.CurrentLabelAtStart,当view移动时,当前分组的标签附着在view的开始。
- ViewSection.NextLabelAtEnd,当view移动时,下一个分组标签附着在view的尾端。
11.2 XmlListModel
XmlListModel用于从XML数据中直接创建一个只读的model,可以用作其他view元素的数据源
XmlListModel使用XPath表达式来提取XML文档中的数据
<videos.xml>
<videos><video name='冰雪奇缘' date='2013-11-19' ><attr tag='导演'>詹妮弗·李</attr><attr tag='演员'>伊迪娜·门泽尔/克里斯汀·贝尔</attr><attr tag='评分'>9.2</attr><attr tag='简介'>在四面环海、风景如画的阿伦达王国,生活着两位可爱美丽的小公主,艾莎和安娜。艾莎天生具有制造冰雪的能...</attr><poster img='http://g3.ykimg.com/0516000052D779CD67583960490A8E1A' /><page link='http://v.youku.com/v_show/id_XNjk1ODc2NDMy.html' /><playtimes>12184709</playtimes></video><video name='功夫' date='2004-12-23' ><attr tag='导演'>周星驰</attr><attr tag='演员'>周星驰/元秋/元华/林子聪/梁小龙/陈国坤</attr><attr tag='评分'>7.0</attr><attr tag='简介'>1940年代的上海,自小受尽欺辱的街头混混阿星(周星驰 饰)为了能出人头地,可谓窥见机会的缝隙就往...</attr><poster img='http://g1.ykimg.com/0516000051BAD11A67583912FF0277C1' /><page link='http://v.qq.com/cover/u/uiq0rxuywu508qr.html' /><playtimes>4012749</playtimes></video><video name='西游·降魔篇' date='2013-02-10'><attr tag='导演'>周星驰</attr><attr tag='演员'>舒淇/文章/黄渤/李尚正/陈炳强/周秀娜</attr><attr tag='评分'>8.1</attr><attr tag='简介'>大唐年间妖魔横行,一小渔村因为饱受鱼妖之害请来道士(冯勉恒 饰)除妖,年轻驱魔人陈玄奘(文章 饰)...</attr><poster img='http://g2.ykimg.com/0516000051B436EB67583928E30DCCDD' /><page link='http://v.youku.com/v_show/id_XNTI2Mzg4NjAw.html' /><playtimes>25421498</playtimes></video><video name='小时代' date='2013-06-27' ><attr tag='导演'>郭敬明</attr><attr tag='演员'>杨幂/郭采洁/郭碧婷/谢依霖/柯震东/凤小岳</attr><attr tag='评分'>8.9</attr><attr tag='简介'>这是一个梦想闪耀的时代,一个理想冷却的时代;这是最坏的时代,这也是最好的时代,这是我们的小时代。在...</attr><poster img='http://g1.ykimg.com/0516000051F22C1C67583931E8015597' /><page link='http://v.youku.com/v_show/id_XNTg3NjkzMzIw.html' /><playtimes>99075808</playtimes></video><video name='倩女幽魂' date='1987-07-18'><attr tag='导演'>程小东</attr><attr tag='演员'>张国荣/王祖贤/午马</attr><attr tag='评分'>8.1</attr><attr tag='简介'>书生宁采臣(张国荣 饰)收账不成,无处可归,遂夜宿鬼寺兰若寺,遇上侠士燕赤霞(午马 饰),二人成为...</attr><poster img='http://g2.ykimg.com/051600004FC32F0797927377D9052FBF' /><page link='http://v.youku.com/v_show/id_XMjE0ODk3MjUy.html' /><playtimes>1579516</playtimes></video><video name='那些年,我们一起追的女孩' date='2011-08-19' ><attr tag='导演'>九把刀</attr><attr tag='演员'>柯震东/陈妍希/郝邵文</attr><attr tag='评分'>8.5</attr><attr tag='简介'>青春是一场大雨。即使感冒了,还盼望回头再淋它一次。人生就是不停的战斗,在还没有获得女神青睐时,左手...</attr><poster img='http://g3.ykimg.com/05160000531420D26758391C5C08485A' /><page link='http://v.qq.com/cover/t/tu0bpgju3a1xno6.html' /><playtimes>3807121</playtimes></video>
</videos>
<main.qml>
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.XmlListModel 2.15Window {width: 560height: 300color: "#EEEEEE"visible: trueComponent {id: videoModelXmlListModel {source: "videos.xml"id: xmlModelquery: "/videos/video"XmlRole {name: "name"query: "@name/string()"}XmlRole {name: "date"query: "@date/string()"}XmlRole {name: "img"query: "poster/@img/string()"}XmlRole {name: "director_tag"query: "attr[1]/@tag/string()"}XmlRole {name: "director "query: "attr[1]/string()"}XmlRole {name: "actor_tag"query: "attr[2]/@tag/string()"}XmlRole {name: "actor"query: "attr[2]/string()
"}XmlRole {name: "rating"query: "attr[3]/number()"}XmlRole {name: "desc"query: "attr[4]/string()"}XmlRole {name: "playtimes"query: "playtimes/number()"}}}Component {id: videoDelegateItem {id: wrapperwidth: listView.widthheight: 120MouseArea {anchors.fill: parentonClicked: wrapper.ListView.view.currentIndex = index}Image {id: posteranchors.left: parent.leftanchors.top: parent.topsource: imgwidth: 80height: 120fillMode: Image.PreserveAspectFit}ColumnLayout {anchors.left: poster.rightanchors.leftMargin: 4anchors.right: wrapper.rightanchors.top: poster.topheight: parent.heightspacing: 2Text {Layout.fillWidth: truetext: "<b>" + name + "</b>(" + rating + "," + playtimes + ")"color: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: dateLayout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: director_tag + ": <font color=\"#0000aa\">" + director + "</font>"Layout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: actor_tag + " : <font color=\"#0000aa\"> " + actor + "</font>"Layout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: descLayout.fillHeight: trueLayout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 16wrapMode: Text.WrapmaximumLineCount: 2elide: Text.ElideRight}}}}ListView {id: listViewanchors.fill: parentspacing: 4delegate: videoDelegatemodel: videoModel.createObject(listView)focus: truehighlight: Rectangle {width: parent.widthcolor: "lightblue"}}
}
source
:指定XmlListModel使用的XML文档的位置
xml
:指定作为model数据源头的XML字符串,utf-8编码,优先生效
query
:一个XPath表达式,以"/“或”//"开始,和XmlRole的query结合使用
roles
:XmlRole对象的列表,XmlListModel通过它来从XML文档中提取数据
count
:当前model内数据的个数
namespaceDeclarations
:保存在XPath中使用的命名空间
status
:model的当前状态
- XmlListModel.Null
- XmlListModel.Ready
- XmlListModel.Loading
- XmlListModel.Error
progress
:表示当前XML文档的下载进度,real类型,从0.0到1.0
get()
:得到索引位置的数据对象,然后可以根据role-name访问数据
reload()
:重新加载model,可以通过指定关键角色来只更新和关键角色匹配的数据
11.3 使用C++ Model
ListView可以使用C++中定义的Model,XmlListModel就是C++实现(QQuickXmlListModel)
C++实现Model必须从QAbstractItemModel
或QAbstractListModel
继承实现
<videoListModel.h>
#ifndef VIDEOLISTMODEL_H
#define VIDEOLISTMODEL_H
#include <QAbstractListModel>
class VideoListModelPrivate;
class VideoListModel : public QAbstractListModel {Q_OBJECTQ_PROPERTY(QString source READ source WRITE setSource)
public:VideoListModel(QObject *parent = 0);~VideoListModel();int rowCount(const QModelIndex &parent) const;QVariant data(const QModelIndex &index, int role) const;QHash<int, QByteArray> roleNames() const;QString source() const;void setSource(const QString &filePath);Q_INVOKABLE QString errorString() const;Q_INVOKABLE bool hasError() const;Q_INVOKABLE void reload();Q_INVOKABLE void remove(int index);private:VideoListModelPrivate *m_dptr;
};
#endif
<videoListModel.cpp>
#include "videoListModel.h"
#include <QDebug>
#include <QFile>
#include <QVector>
#include <QXmlStreamReader>
typedef QVector<QString> VideoData;
class VideoListModelPrivate {
public:VideoListModelPrivate() : m_bError(false) {int role = Qt::UserRole;m_roleNames.insert(role++, "name");m_roleNames.insert(role++, "date");m_roleNames.insert(role++, "director_tag");m_roleNames.insert(role++, "director");m_roleNames.insert(role++, "actor_tag");m_roleNames.insert(role++, "actor");m_roleNames.insert(role++, "rating_tag");m_roleNames.insert(role++, "rating");m_roleNames.insert(role++, "desc_tag");m_roleNames.insert(role++, "desc");m_roleNames.insert(role++, "img");m_roleNames.insert(role++, "playpage");m_roleNames.insert(role++, "playtimes");}~VideoListModelPrivate() { clear(); }void load() {QXmlStreamReader reader;QFile file(m_strXmlFile);if (!file.exists()) {m_bError = true;m_strError = "File Not Found!";return;}if (!file.open(QFile::ReadOnly)) {m_bError = true;m_strError = file.errorString();return;}reader.setDevice(&file);QStringRef elementName;VideoData *video;while (!reader.atEnd()) {reader.readNext();if (reader.isStartElement()) {elementName = reader.name();if (elementName == "video") {video = new VideoData();QXmlStreamAttributes attrs = reader.attributes();video->append(attrs.value("name").toString());video->append(attrs.value("date").toString());} else if (elementName == "attr") {video->append(reader.attributes().value("tag").toString());video->append(reader.readElementText());} else if (elementName == "poster") {video->append(reader.attributes().value("img").toString());} else if (elementName == "page") {video->append(reader.attributes().value("link").toString());} else if (elementName == "playtimes") {video->append(reader.readElementText());}} else if (reader.isEndElement()) {elementName = reader.name();if (elementName == "video") {m_videos.append(video);video = 0;}}}file.close();if (reader.hasError()) {m_bError = true;m_strError = reader.errorString();}}void reset() {m_bError = false;m_strError.clear();clear();}void clear() {int count = m_videos.size();if (count > 0) {for (int i = 0; i < count; i++) {delete m_videos.at(i);}m_videos.clear();}}QString m_strXmlFile;QString m_strError;bool m_bError;QHash<int, QByteArray> m_roleNames;QVector<VideoData *> m_videos;
};VideoListModel::VideoListModel(QObject *parent): QAbstractListModel(parent), m_dptr(new VideoListModelPrivate) {}
VideoListModel::~VideoListModel() { delete m_dptr; }
int VideoListModel::rowCount(const QModelIndex &parent) const {return m_dptr->m_videos.size();
}
QVariant VideoListModel::data(const QModelIndex &index, int role) const {VideoData *d = m_dptr->m_videos[index.row()];return d->at(role - Qt::UserRole);
}
QHash<int, QByteArray> VideoListModel::roleNames() const {return m_dptr->m_roleNames;
}
QString VideoListModel::source() const { return m_dptr->m_strXmlFile; }
void VideoListModel::setSource(const QString &filePath) {m_dptr->m_strXmlFile = filePath;reload();if (m_dptr->m_bError) {qDebug() << " VideoListModel,error - " << m_dptr->m_strError;}
}
QString VideoListModel::errorString() const { return m_dptr->m_strError; }
bool VideoListModel::hasError() const { return m_dptr->m_bError; }
void VideoListModel::reload() {beginResetModel();m_dptr->reset();m_dptr->load();endResetModel();
}
void VideoListModel::remove(int index) {beginRemoveRows(QModelIndex(), index, index);delete m_dptr->m_videos.takeAt(index);endRemoveRows();
}
<main.cpp>
#include "videoListModel.h"
#include <QApplication>
#include <QColor>
#include <QQmlApplicationEngine>
#include <QtQml>int main(int argc, char *argv[]) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endifQApplication app(argc, argv);qmlRegisterType<VideoListModel>("an.qt.CModel", 1, 0, "VideoListModel");QQmlApplicationEngine engine;engine.load("qrc:/main.qml");return app.exec();
}
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import an.qt.CModel 1.0Window {width: 560height: 300color: "#EEEEEE"visible: trueComponent {id: videoDelegateItem {id: wrapperwidth: listView.widthheight: 120MouseArea {anchors.fill: parentonClicked: wrapper.ListView.view.currentIndex = index}Image {id: posteranchors.left: parent.leftanchors.top: parent.topsource: imgwidth: 80height: 120fillMode: Image.PreserveAspectFit}ColumnLayout {anchors.left: poster.rightanchors.leftMargin: 4anchors.right: wrapper.rightanchors.top: poster.topheight: parent.heightspacing: 2Text {Layout.fillWidth: truetext: "<b>" + name + "</b>(" + rating + "," + playtimes + ")"color: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: dateLayout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: director_tag + ": <font color=\"#0000aa\">" + director + "</font>"Layout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: actor_tag + " : <font color=\"#0000aa\"> " + actor + "</font>"Layout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 18elide: Text.ElideRight}Text {text: descLayout.fillHeight: trueLayout.fillWidth: truecolor: wrapper.ListView.isCurrentItem ? "blue" : "black"font.pixelSize: 16wrapMode: Text.WrapmaximumLineCount: 2elide: Text.ElideRight}}}}ListView {id: listViewanchors.fill: parentspacing: 4delegate: videoDelegatemodel: VideoListModel {source: ".\\videos.xml"}focus: truehighlight: Rectangle {width: parent.widthcolor: "lightblue"}}
}
当允许在QML中修改C++实现的Model时,比如删除,就需要做如下动作(如删除):
- 调用基类的beginRemoveRows()
- 针对要删除的数据进行特定处理,如释放内存
- 调用基类的endRemoveRows()
11.4 TableView
TableView和ListView类似,多出了滚动条、挑选、可调整尺寸的表头等特性
TableView的数据也通过Model提供,可以使用ListModel、XmlListModel或使用C++从、QAbstractItemModel
和QAbstractTableModel
等继承来实现Model
11.5 GridView
GridView和ListView类似,不同在于Item的呈现方式
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.XmlListModel 2.15Window {width: 480height: 400visible: trueComponent {id: videoModelXmlListModel {source: "videos.xml"id: xmlModelquery: "/videos/video"XmlRole {name: "name"query: "@name/string()"}XmlRole {name: "img"query: "poster/@img/string()"}XmlRole {name: "rating"query: "attr[3]/number()"}}}Component {id: videoDelegateItem {id: wrapperwidth: videoView.cellWidthheight: videoView.cellHeightMouseArea {anchors.fill: parentonClicked: wrapper.GridView.view.currentIndex = index}Image {id: posteranchors.horizontalCenter: parent.horizontalCenteranchors.top: parent.topanchors.topMargin: 3source: imgwidth: 100height: 150fillMode: Image.PreserveAspectFit}Text {anchors.top: poster.bottomanchors.topMargin: 4width: parent.widthtext: namecolor: wrapper.GridView.isCurrentItem ? "blue" : "black"font.pixelSize: 18horizontalAlignment: Text.AlignHCenterelide: Text.ElideMiddle}}}GridView {id: videoViewanchors.fill: parentcellWidth: 120cellHeight: 190delegate: videoDelegatemodel: videoModel.createObject(videoView)focus: truehighlight: Rectangle {height: videoView.cellHeight - 8color: "lightblue"}}
}
flow
:指定Item的流模式,GridView.LeftToRight
和GridView.TopToBottom
cellWidth
:单元格宽度
cellHeight
:单元格高度
11.6 Repeater
Repeater用于创建多个基于Item的组件,丢给它的父(通常是定位器或布局管理器)来管理
count
:指定要创建多少个基于Item的对象
model
:指定数据类型,数字、字符串列表、对象列表、ListModel等常见的model
delegate
:待实例化的组件,默认属性,定义时通常不显示初始化
itemAt(index)
:根据索引返回对应的delegate实例
11.6.1 model为数字
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.XmlListModel 2.15Window {width: 480height: 400visible: trueRowLayout {anchors.fill: parentspacing: 4Repeater {model: 8Rectangle {width: 46height: 30color: "steelblue"Text {anchors.fill: parentcolor: "black"font.pointSize: 14verticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentertext: index}}}}
}
11.6.2 model为字符串列表
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.XmlListModel 2.15Window {width: 480height: 400visible: trueRow {anchors.centerIn: parentspacing: 8Repeater {model: ["Hello", "Qt", "Quick"]Text {color: "blue"font.pointSize: 18font.bold: trueverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCentertext: modelData}}}
}
11.6.3 model为对象列表
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.XmlListModel 2.15Window {width: 480height: 400visible: trueColumn {anchors.fill: parentanchors.margins: 4spacing: 4Repeater {model: [{"name": "Zhang San","mobile": "13888888888
"}, {"name": "Wang Er","mobile": "13999999999
"}, {"name": "Liu Wu","mobile": "15866666666"}]Row {height: 30Text {width: 100color: "blue"font.pointSize: 13font.bold: trueverticalAlignment: Text.AlignVCentertext: modelData.name}Text {width: 200font.pointSize: 13verticalAlignment: Text.AlignVCentertext: modelData.mobile}}}}
}
11.6.4 model为ListModel
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.XmlListModel 2.15Window {width: 480height: 400visible: trueColumn {anchors.fill: parentanchors.margins: 4spacing: 4Repeater {model: ListModel {ListElement {name: "MI4"cost: "1999"manufacturer: "Xiaomi"}ListElement {name: "MX4"cost: "1999"manufacturer: "Meizu"}ListElement {name: "iPhone6"cost: "5500"manufacturer: "Apple"}ListElement {name: "C199"cost: "1599"manufacturer: "Huawei"}}Row {height: 30Text {width: 120color: "blue"font.pointSize: 14font.bold: trueverticalAlignment: Text.AlignVCentertext: name}Text {width: 100font.pointSize: 14verticalAlignment: Text.AlignVCentertext: cost}Text {width: 100font.pointSize: 12verticalAlignment: Text.AlignVCentertext: manufacturer}}}}
}