《qt quick核心编程》笔记四

news2025/6/8 7:17:21

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.15

Window {
	width: 560
	height: 300
	color: "#EEEEEE"
	visible: true

	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: "B199"
				cost: "1590"
				manufacturer: "HuaWei"
			}
			ListElement {
				name: "MI 2S"
				cost: "1999"
				manufacturer: "XiaoMi"
			}
			ListElement {
				name: "GALAXY S5"
				cost: "4699"
				manufacturer: "Samsung"
			}
		}
	}

	Component {
		id: headview
		Item {
			width: parent.width
			height: 30
			RowLayout {
				anchors.verticalCenter: parent.verticalCenter

				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: footerView

		Item {
			signal clean
			signal add
			signal insert
			signal move
			property alias text: txt.text
			width: listView.width
			height: 30
			Text {
				id: txt
				anchors.left: parent.left
				color: "green"
				height: parent.height
				verticalAlignment: Text.AlignVCenter
			}

			Button {
				id: clearAll
				anchors.right: parent.right
				anchors.verticalCenter: parent.verticalCenter
				height: parent.height
				text: "clear"
				onClicked: {
					clean()
				}
			}

			Button {
				id: addOne
				anchors.right: clearAll.left
				anchors.rightMargin: 5
				anchors.verticalCenter: parent.verticalCenter
				height: parent.height
				text: "Add"
				onClicked: {
					add()
				}
			}

			Button {
				id: insertOne
				anchors.right: addOne.left
				anchors.rightMargin: 5
				anchors.verticalCenter: parent.verticalCenter
				height: parent.height
				text: "Insert"
				onClicked: {
					insert()
				}
			}

			Button {
				id: moveDown
				anchors.right: insertOne.left
				anchors.rightMargin: 5
				anchors.verticalCenter: parent.verticalCenter
				height: parent.height
				text: "MoveDown"
				onClicked: {
					move()
				}
			}
		}
	}

	Component {
		id: phoneDelegate
		Item {
			id: wrapper
			width: listView.width
			height: 30
			MouseArea {
				anchors.fill: parent
				onClicked: wrapper.ListView.view.currentIndex = index
				onDoubleClicked: wrapper.ListView.view.model.remove(index)
			}

			RowLayout {
				anchors.verticalCenter: parent.verticalCenter
				width: parent.width
				Text {
					id: col1
					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
		header: headview
		footer: footerView
		model: phoneModel.createObject(listView)
		focus: true
		highlight: Rectangle {
			color: "lightblue"
		}

		add: Transition {
			ParallelAnimation {
				NumberAnimation {
					property: "opacity"
					from: 0
					to: 1.0
					duration: 1000
				}
				NumberAnimation {
					property: "y"
					from: 0
					duration: 1000
				}
			}
		}

		displaced: Transition {
			SpringAnimation {
				properties: "y"
				spring: 3
				damping: 0.1
				epsilon: 0.25
			}
		}

		remove: Transition {
			SequentialAnimation {
				NumberAnimation {
					properties: "y"
					to: 0
					duration: 600
				}

				NumberAnimation {
					properties: "opacity"
					to: 0
					duration: 400
				}
			}
		}

		move: Transition {
			NumberAnimation {
				properties: "y"
				duration: 300
				easing.type: Easing.InQuart
			}
		}

		populate: Transition {
			NumberAnimation {
				property: "opacity"
				from: 0
				to: 1.0
				duration: 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)
			}
		}
	}
}
  1. ListModel专门用于定义列表数据,内部维护一个ListElement的列表,一个ListElement对象代表一个数据。
  2. 多个role构成一个ListElementrole包含一个名字和一个值,名字必须以小写字母开头,值必须是简单的常量(字符串、布尔值、数字、枚举值)
  3. ListElement中定义的role,可以在Delegate中通过名称访问
  4. Delegate使用Row管理Text对象来展现role,Text对象的text属性对应于role的名称
  5. ListView的delegate属性类型是Component,Component的顶层元素是Row,Row内嵌三个Text对象来展示Model定义的ListElement的三个role
  6. ListView给delegate暴露一个index属性,代表当前delegate示例对应的Item的索引位置,必要时可通过它来访问数据
  7. ListView定义delayRemoveisCurrentItemnextSectionpreviousSectionsectionview等附加属性,以及addremove两个附加信号,可以在delegate中直接访问(只有delegate的顶层Item才能直接使用这些附加属性和信号,非顶层Item则需通过顶层Item的id来访问这些附加属性)
  8. 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.15

Window {
	width: 560
	height: 300
	color: "#EEEEEE"
	visible: true

	Component {
		id: phoneModel
		ListModel {
			// Apple section
			ListElement {
				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 section
			ListElement {
				name: "B199"
				cost: "1590"
				manufacturer: "HuaWei"
			}

			// Sumsung section
			ListElement {
				name: "GALAXY S4"
				cost: "3099"
				manufacturer: "Samsung"
			}
			ListElement {
				name: "C8816D"
				cost: "590"
				manufacturer: "HuaWei"
			}
			ListElement {
				name: "GALAXY S5"
				cost: "4699"
				manufacturer: "Samsung"
			}
			// XiaoMi section
			ListElement {
				name: "MI 2S"
				cost: "1999"
				manufacturer: "XiaoMi"
			}
			ListElement {
				name: "MI 3"
				cost: "1999"
				manufacturer: "XiaoMi"
			}
		}
	}
	Component {
		id: phoneDelegate
		Item {
			id: wrapper
			width: parent.width
			height: 30
			ListView.onAdd: {
				console.log("count:", ListView.view.count)
			}
			MouseArea {
				anchors.fill: parent
				onClicked: wrapper.ListView.view.currentIndex = index
			}
			RowLayout {
				anchors.left: parent.left
				anchors.verticalCenter: parent.verticalCenter
				spacing: 8
				Text {
					id: col1
					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 ? "green" : "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
				}
			}
		}
	}
	Component {
		id: sectionHeader
		Rectangle {
			width: parent.width
			height: childrenRect.height
			color: "lightsteelblue"
			Text {
				text: section
				font.bold: true
				font.pixelSize: 20
			}
		}
	}
	ListView {
		id: listView
		anchors.fill: parent
		delegate: phoneDelegate
		model: phoneModel.createObject(listView)
		focus: true
		highlight: Rectangle {
			color: "lightblue"
		}
		section.property: "manufacturer"
		section.criteria: ViewSection.FullString
		section.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.15

Window {
	width: 560
	height: 300
	color: "#EEEEEE"
	visible: true

	Component {
		id: videoModel
		XmlListModel {
			source: "videos.xml"
			id: xmlModel
			query: "/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: videoDelegate
		Item {
			id: wrapper
			width: listView.width
			height: 120
			MouseArea {
				anchors.fill: parent
				onClicked: wrapper.ListView.view.currentIndex = index
			}
			Image {
				id: poster
				anchors.left: parent.left
				anchors.top: parent.top
				source: img
				width: 80
				height: 120
				fillMode: Image.PreserveAspectFit
			}
			ColumnLayout {
				anchors.left: poster.right
				anchors.leftMargin: 4
				anchors.right: wrapper.right
				anchors.top: poster.top
				height: parent.height
				spacing: 2
				Text {
					Layout.fillWidth: true
					text: "<b>" + name + "</b>(" + rating + "," + playtimes + ")"
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: date
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: director_tag + ": <font color=\"#0000aa\">" + director + "</font>"
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: actor_tag + " : <font color=\"#0000aa\"> " + actor + "</font>"
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: desc
					Layout.fillHeight: true
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 16
					wrapMode: Text.Wrap
					maximumLineCount: 2
					elide: Text.ElideRight
				}
			}
		}
	}
	ListView {
		id: listView
		anchors.fill: parent
		spacing: 4
		delegate: videoDelegate
		model: videoModel.createObject(listView)
		focus: true
		highlight: Rectangle {
			width: parent.width
			color: "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必须从QAbstractItemModelQAbstractListModel继承实现
<videoListModel.h>

#ifndef VIDEOLISTMODEL_H
#define VIDEOLISTMODEL_H
#include <QAbstractListModel>
class VideoListModelPrivate;
class VideoListModel : public QAbstractListModel {
  Q_OBJECT
  Q_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);
#endif
  QApplication 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.0

Window {
	width: 560
	height: 300
	color: "#EEEEEE"
	visible: true

	Component {
		id: videoDelegate
		Item {
			id: wrapper
			width: listView.width
			height: 120
			MouseArea {
				anchors.fill: parent
				onClicked: wrapper.ListView.view.currentIndex = index
			}
			Image {
				id: poster
				anchors.left: parent.left
				anchors.top: parent.top
				source: img
				width: 80
				height: 120
				fillMode: Image.PreserveAspectFit
			}
			ColumnLayout {
				anchors.left: poster.right
				anchors.leftMargin: 4
				anchors.right: wrapper.right
				anchors.top: poster.top
				height: parent.height
				spacing: 2
				Text {
					Layout.fillWidth: true
					text: "<b>" + name + "</b>(" + rating + "," + playtimes + ")"
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: date
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: director_tag + ": <font color=\"#0000aa\">" + director + "</font>"
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: actor_tag + " : <font color=\"#0000aa\"> " + actor + "</font>"
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 18
					elide: Text.ElideRight
				}
				Text {
					text: desc
					Layout.fillHeight: true
					Layout.fillWidth: true
					color: wrapper.ListView.isCurrentItem ? "blue" : "black"
					font.pixelSize: 16
					wrapMode: Text.Wrap
					maximumLineCount: 2
					elide: Text.ElideRight
				}
			}
		}
	}
	ListView {
		id: listView
		anchors.fill: parent
		spacing: 4
		delegate: videoDelegate
		model: VideoListModel {
			source: ".\\videos.xml"
		}
		focus: true
		highlight: Rectangle {
			width: parent.width
			color: "lightblue"
		}
	}
}

当允许在QML中修改C++实现的Model时,比如删除,就需要做如下动作(如删除):

  1. 调用基类的beginRemoveRows()
  2. 针对要删除的数据进行特定处理,如释放内存
  3. 调用基类的endRemoveRows()

11.4 TableView

TableView和ListView类似,多出了滚动条、挑选、可调整尺寸的表头等特性
TableView的数据也通过Model提供,可以使用ListModel、XmlListModel或使用C++从、QAbstractItemModelQAbstractTableModel等继承来实现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.15

Window {
	width: 480
	height: 400
	visible: true

	Component {
		id: videoModel
		XmlListModel {
			source: "videos.xml"
			id: xmlModel
			query: "/videos/video"
			XmlRole {
				name: "name"
				query: "@name/string()"
			}
			XmlRole {
				name: "img"
				query: "poster/@img/string()"
			}
			XmlRole {
				name: "rating"
				query: "attr[3]/number()"
			}
		}
	}
	Component {
		id: videoDelegate
		Item {
			id: wrapper
			width: videoView.cellWidth
			height: videoView.cellHeight
			MouseArea {
				anchors.fill: parent
				onClicked: wrapper.GridView.view.currentIndex = index
			}
			Image {
				id: poster
				anchors.horizontalCenter: parent.horizontalCenter
				anchors.top: parent.top
				anchors.topMargin: 3
				source: img
				width: 100
				height: 150
				fillMode: Image.PreserveAspectFit
			}
			Text {
				anchors.top: poster.bottom
				anchors.topMargin: 4
				width: parent.width
				text: name
				color: wrapper.GridView.isCurrentItem ? "blue" : "black"
				font.pixelSize: 18
				horizontalAlignment: Text.AlignHCenter
				elide: Text.ElideMiddle
			}
		}
	}
	GridView {
		id: videoView
		anchors.fill: parent
		cellWidth: 120
		cellHeight: 190
		delegate: videoDelegate
		model: videoModel.createObject(videoView)
		focus: true
		highlight: Rectangle {
			height: videoView.cellHeight - 8
			color: "lightblue"
		}
	}
}

flow:指定Item的流模式,GridView.LeftToRightGridView.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.15

Window {
	width: 480
	height: 400
	visible: true

	RowLayout {
		anchors.fill: parent
		spacing: 4
		Repeater {
			model: 8
			Rectangle {
				width: 46
				height: 30
				color: "steelblue"
				Text {
					anchors.fill: parent
					color: "black"
					font.pointSize: 14
					verticalAlignment: Text.AlignVCenter
					horizontalAlignment: Text.AlignHCenter
					text: 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.15

Window {
	width: 480
	height: 400
	visible: true

	Row {
		anchors.centerIn: parent
		spacing: 8
		Repeater {
			model: ["Hello", "Qt", "Quick"]
			Text {
				color: "blue"
				font.pointSize: 18
				font.bold: true
				verticalAlignment: Text.AlignVCenter
				horizontalAlignment: Text.AlignHCenter
				text: 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.15

Window {
	width: 480
	height: 400
	visible: true

	Column {
		anchors.fill: parent
		anchors.margins: 4
		spacing: 4
		Repeater {
			model: [{
					"name": "Zhang San",
					"mobile": "13888888888
"
				}, {
					"name": "Wang Er",
					"mobile": "13999999999
"
				}, {
					"name": "Liu Wu",
					"mobile": "15866666666"
				}]
			Row {
				height: 30
				Text {
					width: 100
					color: "blue"
					font.pointSize: 13
					font.bold: true
					verticalAlignment: Text.AlignVCenter
					text: modelData.name
				}
				Text {
					width: 200
					font.pointSize: 13
					verticalAlignment: Text.AlignVCenter
					text: 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.15

Window {
	width: 480
	height: 400
	visible: true

	Column {
		anchors.fill: parent
		anchors.margins: 4
		spacing: 4
		Repeater {
			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: 30
				Text {
					width: 120
					color: "blue"
					font.pointSize: 14
					font.bold: true
					verticalAlignment: Text.AlignVCenter
					text: name
				}
				Text {
					width: 100
					font.pointSize: 14
					verticalAlignment: Text.AlignVCenter
					text: cost
				}
				Text {
					width: 100
					font.pointSize: 12
					verticalAlignment: Text.AlignVCenter
					text: manufacturer
				}
			}
		}
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/886264.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年武汉市中等职业教育网络安全赛项竞赛试题——综合靶机渗透测试解析教程

任务一&#xff1a;综合靶机渗透测试 任务环境说明&#xff1a; 服务器场景&#xff1a;ZHCS-2&#xff08;关闭连接&#xff09; 服务器场景操作系统&#xff1a;版本不详 大家需要靶机环境可以私信我获取 1.扫描目标靶机将靶机开放的所有端口&#xff0c;当作flag提交&am…

【学会动态规划】乘积为正数的最长子数组长度(21)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

ESP32 历程解析

最近在用esp32做项目&#xff0c;记录一下 使用的esp32 的官方历程的作用 一、USB历程 1.host 1) cdc_acm_host 这个历程可以用来驱动 usb的虚拟串口从机 cdc__vcp 和 cdc_acm 方式虚拟的串口需要根据自己的设备 修改 VID 和 PID /*cdc-acm*/ #define EXAMPLE_US…

非计算机科班如何顺利转码进入计算机领域?

文章目录 如何规划才能实现转码&#xff1f;计算机岗位发展前景&#xff1f;现阶段转码 总结 &#x1f389;欢迎来到Java学习路线专栏~探索非计算机科班如何顺利转码进入计算机领域 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客&#x1f3…

8.16日 算法做题【4道题】

8. 分行从上往下打印二叉树 &#xff08; 利用两个队列遍历 &#xff09; 利用数组个数 进行遍历 原题链接 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(N…

工业设备状态监测如何实现从技术试点到规模实施?

工业企业正逐步引入基于人工智能的持续监控设备健康状况系统&#xff0c;这对工业维护领域带来了颠覆性的影响。然而&#xff0c;与任何新技术一样&#xff0c;要实现其最大价值&#xff0c;需要克服一些挑战。在数字化转型中&#xff0c;成功实现工业设备状态监测的从技术试点…

企业什么时候需要PMO?PMO的作用和职责是什么?

在当今复杂的商业环境中&#xff0c;企业依靠高效的项目管理来推动成功和实现战略目标。这就是**项目管理办公室**&#xff08;PMO&#xff09;发挥作用的地方。那么&#xff0c;PMO在企业中的作用和职责究竟是什么&#xff1f;本文就这个问题来做个探讨。 PMO &#xff08;项…

第 3 章 稀疏数组和队列(1)

3.1 稀疏 sparsearray 数组 3.1.1先看一个实际的需求 编写的五子棋程序中&#xff0c;有存盘退出和续上盘的功能。 分析问题: 因为该二维数组的很多值是默认值 0.因此记录了很多没有意义的数据.->稀疏数组 3.1.2基本介绍 当一个数组中大部分元素为 0&#xff0c;或者为同…

python进阶之符号计算概述SymPy

一、概述 1.1SymPy简介 SymPy 是一个由 Python 编写的符号计算库&#xff0c;它的目标是成为一个全功能的计算机代数系统&#xff0c;同时保持代码简洁、易于理解和扩展。它完全由 Python 写成&#xff0c;不依赖于外部库。SymPy 支持符号计算、高精度计算、模式匹配、绘图、解…

搭建Excel服务器

1、下载Excel服务器 下载地址 2、解压文件 3、打开服务器 4、服务器运行信息 5、连接测试 打开客户端 6、登录到服务器 默认账号 密码 admin 3 修改文件保存路径(服务器端点击配置) 7、客户端整体界面 8、配置权限 9、设计模板 10、其他用户登录就可以填写信息 11、用户&#…

JVM基础了解

JVM 是java虚拟机。 作用&#xff1a;运行并管理java源码文件锁生成的Class文件&#xff1b;在不同的操作系统上安装不同的JVM&#xff0c;从而实现了跨平台的保证。一般在安装完JDK或者JRE之后&#xff0c;其中就已经内置了JVM&#xff0c;只需要将Class文件交给JVM即可 写好的…

【MCUXpresso for VS Code】-- 工程搭建后期维护

​ 基于上一篇文章【MCUXpresso for VS Code】 – 基于VSCode搭建nxp mcu工程_ 本章将介绍工程搭建完成后&#xff0c;后期维护问题。 1.MCUXpresso 插件窗口 该窗口&#xff0c;通常用在编译调试&#xff0c;工程导入导出等场合。编译和调试除了点击图标外&#xff0c;也可以…

python简单计算器

附赠源码&#xff1a; import PySimpleGUI as sg import relistsNumber [[AC, (, ), %],[1, 2, 3, ],[4, 5, 6, -],[7, 8, 9, x],[0, ., , ], ]layout [[sg.Text(font(黑体, 10), key-msg-)],[sg.Multiline(,key-show-,s(22, 10),font(黑体, 14),border_width1,expand_xTrue…

Java进阶(3)——手动实现ArrayList 源码的初步理解分析 数组插入数据和删除数据的问题

目录 引出手动实现ArrayList定义接口MyList<T>写ArrayList的实现类增加元素删除元素 写测试类进行测试数组插入数据? 总结 引出 1.ArrayList的结构分析&#xff0c;可迭代接口&#xff0c;是List的实现&#xff1b; 2.数组增加元素和删除元素的分析&#xff0c;何时扩容…

Spring三级缓存解决循环依赖问题

一、Bean对象的创建过程 一般的Bean对象如下&#xff1a;首先通过构造器构造一个普通对象&#xff0c;然后进行依赖注入&#xff0c;再进行一些初始化操作&#xff0c;初始化后根据AOP生成代理对象&#xff0c;最后再放入单例池map&#xff0c;这个单例池map就是一级缓存。 …

Data Abstract for .NET and Delphi Crack

Data Abstract for .NET and Delphi Crack .NET和Delphi的数据摘要是一套或RAD工具&#xff0c;用于在.NET、Delphi和Mono中编写多层解决方案。NET和Delphi的数据摘要是一个套件&#xff0c;包括RemObjects.NET和Delphi版本的数据摘要。RemObjects Data Abstract允许您创建访问…

DHCP Server

简介 动态主机配置协议 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09; 是 RFC 1541&#xff08;已被 RFC 2131 取代&#xff09;定义的标准协议&#xff0c;该协议允许服务器向客户端动态分配 IP 地址和配置信息。 使用UDP协…

C++MFC 串口通信 上位机

本节介绍 在工业控制中&#xff0c;工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信。串口通信方便易行&#xff0c;应用广泛。 一般情况下&#xff0c;工控机和各智能仪表通过RS485总线进行通信。RS485的通信方式是半双工的&#xff0c;只能由作为主…

软件测试知识库+1,5款顶级自动化测试工具推荐和使用分析

“工欲善其事必先利其器”&#xff0c;在自动化测试领域&#xff0c;自动化测试工具的核心地位不容置疑的。目前市面上有很多可以支持接口测试的工具&#xff0c;在网上随便一搜就可以出来很多&#xff0c;利用自动化测试工具进行接口测试&#xff0c;可以很好的提高测试效率&a…

Web3创新者之夜,与其他开发者一同畅谈波卡生态

Token2049在即&#xff0c;许多开发者都将在9月中齐聚新加坡&#xff0c;一同讨论区块链生态发展及未来。届时将会有超过1万名与会者&#xff0c;并有超过300个赞助商和项目协助支持本次大会。波卡作为跨链互操作性的龙头生态也将参与至本次盛会之中。 为了让波卡社区的成员、贡…