中的模型和视图Qt Quick
大多数应用程序都需要对数据进行格式化并显示数据。Qt Quick 引入了模型、视图和委托的概念来显示数据。这些概念将数据可视化进行了模块化,以便开发人员或设计师能够控制数据的各个方面。 开发人员只需对数据进行微调,即可将列表视图替换为网格视图。同样,将数据实例封装在委托中,可让开发人员决定如何呈现或处理数据。

- 模型(Model)——包含数据及其结构。QML 提供了多种用于创建模型的类型。
- 视图(View)——用于显示数据的容器。视图可以以列表或网格的形式展示数据。
- 委托(Delegate)——决定数据在视图中应如何呈现。委托会获取模型中的每个数据单元并将其封装起来。可通过委托访问数据。委托还可以将数据写回可编辑的模型中(例如在TextField 的onAccepted处理程序中)。
要可视化数据,请将视图的model 属性绑定到模型,并将delegate 属性绑定到组件或其他兼容类型。
使用视图显示数据
视图是项目集合的容器。它们功能丰富,并且可以进行自定义以满足样式或行为方面的要求。
Qt Quick 基础图形类型中提供了一组标准视图:
- ListView - 将项目以水平或垂直列表形式排列
- GridView - 在可用空间内将项目以网格形式排列
- PathView - 将项目沿路径排列
- TableView - 将QAbstractTableModel 中的数据以表格形式排列
- TreeView - 将QAbstractItemModel 中的数据以树形结构排列
这些类型各自具有专属的属性和行为。如需了解更多信息,请查阅相关文档。
此外, Qt Quick Controls 还包含一些根据应用程序样式设计的额外视图和委托,例如HorizontalHeaderView 和VerticalHeaderView 。
视图装饰
视图可通过装饰属性(如header 、footer 和section )进行视觉自定义。通过将一个对象(通常是另一个视觉对象)绑定到这些属性上,视图即可被装饰。页脚可能包含一个显示边框的Rectangle 类型,或者页眉可能在列表顶部显示徽标。
假设某个俱乐部希望用其品牌颜色来装饰其会员列表。会员列表位于一个model 中,而delegate 将显示该模型的内容。
ListModel {
id: nameModel
ListElement { name: "Alice" }
ListElement { name: "Bob" }
ListElement { name: "Jane" }
ListElement { name: "Harry" }
ListElement { name: "Wendy" }
}
Component {
id: nameDelegate
Text {
required property string name
text: name
font.pixelSize: 24
width: ListView.view.width
}
}该俱乐部可以通过将视觉对象绑定到header 和footer 属性来装饰会员列表。该视觉对象可以内联定义、定义在另一个文件中,或定义在Component 类型中。
ListView {
anchors.fill: parent
clip: true
model: nameModel
delegate: nameDelegate
header: bannercomponent
footer: Rectangle {
width: parent.width; height: 30;
gradient: clubcolors
}
highlight: Rectangle {
color: "lightgray"
}
}
Component { //instantiated when header is processed
id: bannercomponent
Rectangle {
id: banner
width: parent.width; height: 50
gradient: clubcolors
border {color: "#9EDDF2"; width: 2}
Text {
anchors.centerIn: parent
text: "Club Members"
font.pixelSize: 32
}
}
}
Gradient {
id: clubcolors
GradientStop { position: 0.0; color: "#8EE2FE"}
GradientStop { position: 0.66; color: "#7ED2EE"}
}
鼠标和触摸处理
视图会处理其内容的拖拽和轻扫操作,但不会处理与各个委托对象之间的触摸交互。为了使委托对象能够响应触摸输入(例如设置currentIndex ),委托对象必须提供一个包含适当触摸处理逻辑的MouseArea 。
请注意,如果将highlightRangeMode 设置为StrictlyEnforceRange ,则currentIndex会受到视图拖动/轻扫操作的影响,因为视图将始终确保currentIndex 位于指定的高亮范围内。
ListView 分区
ListView 内容可分组为不同部分,相关列表项将根据所属部分进行标注。此外,各部分还可通过委托进行样式装饰。
一个列表可能包含另一个列表,该列表列出了人员姓名及其所属团队。
ListModel {
id: nameModel
ListElement { name: "Alice"; team: "Crypto" }
ListElement { name: "Bob"; team: "Crypto" }
ListElement { name: "Jane"; team: "QA" }
ListElement { name: "Victor"; team: "QA" }
ListElement { name: "Wendy"; team: "Graphics" }
}
Component {
id: nameDelegate
Text {
required property string name
text: name;
font.pixelSize: 24
anchors.left: parent.left
anchors.leftMargin: 2
}
}ListView 类型具有section 附加属性,该属性可将相邻且相关的类型组合为一个分区。section.property 属性决定使用哪个列表类型属性作为分区。section.criteria 属性可指定分区名称的显示方式,而section.delegate 属性则类似于视图的委托属性。
ListView {
anchors.fill: parent
model: nameModel
delegate: nameDelegate
focus: true
highlight: Rectangle {
color: "lightblue"
width: parent.width
}
section {
property: "team"
criteria: ViewSection.FullString
delegate: Rectangle {
color: "#b0dfb0"
width: parent.width
height: childrenRect.height + 4
Text { anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 16
font.bold: true
text: section
}
}
}
}
视图委托
视图需要一个委托来直观地呈现列表中的项目。视图将根据委托定义的模板来可视化每个列表项目。可以通过index 属性以及项目的属性来访问模型中的项目。
Component {
id: petdelegate
Text {
id: label
font.pixelSize: 24
text: index === 0 ? type + " (default)" : type
required property int index
required property string type
}
}
视图委托的定位
视图的类型将决定项目的布局方式。ListView 会根据orientation 将项目沿直线排列,而GridView 则可将项目以二维网格形式布局。不建议直接绑定x 和y ,因为视图的布局行为始终优先于任何位置绑定。
从委托中访问视图和模型
委托可通过ListView.view 属性访问其绑定的列表视图。同样,委托也可以访问GridView GridView.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 {
id: fruit
required property string name
required property real cost
Text {
text: " Fruit: " + fruit.name
color: fruit.ListView.view.fruit_color
}
Text {
text: " Cost: $" + fruit.cost
}
Text {
text: " Language: " + fruit.ListView.view.model.language
}
}
}
ListView {
property color fruit_color: "green"
model: fruitModel
delegate: fruitDelegate
anchors.fill: parent
}
}模型
数据是通过命名数据角色提供给委托的,委托可以绑定到这些角色上。下面是一个具有两个角色(type和age)的ListModel ,以及一个ListView ,其委托绑定到这些角色以显示它们的值:
import QtQuick
Item {
width: 200
height: 250
ListModel {
id: myModel
ListElement { type: "Dog"; age: 8; noise: "meow" }
ListElement { type: "Cat"; age: 5; noise: "woof" }
}
component MyDelegate : Text {
required property string type
required property int age
text: type + ", " + age
// WRONG: Component.onCompleted: () => console.log(noise)
// The above line would cause a ReferenceError
// as there is no required property noise,
// and the presence of the required properties prevents
// noise from being injected into the scope
}
ListView {
anchors.fill: parent
model: myModel
delegate: MyDelegate {}
}
}在大多数情况下,您应使用必填属性将模型数据传递给委托。如果委托包含必填属性,QML 引擎将检查该必填属性的名称是否与模型角色的名称匹配。如果匹配,该属性将与模型中的相应值建立绑定。
在极少数特殊情况下,您可能希望通过 QML 上下文而非作为必填属性来传递模型属性。如果您的委托中不存在必填属性,则命名角色将作为上下文属性提供:
import QtQuick
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
}
}上下文属性对工具不可见,且会阻碍 Qt Quick Compiler 对您的代码进行优化。这使得推断委托所期望的具体数据变得更加困难。无法从 QML 中显式地填充 QML 上下文。如果您的组件期望通过 QML 上下文传递数据,则只能在通过原生方式提供了正确上下文的位置使用它。 这可以是您自己的 C++ 代码,也可以是周围元素的特定实现。相反,必需属性可以通过多种方式从 QML 或通过原生手段进行设置。因此,通过 QML 上下文传递数据会降低组件的可复用性。
如果模型的属性与委托的属性之间存在命名冲突,则可以使用带修饰符的模型名称来访问这些角色。例如,如果Text 类型具有(非必需的)type 或age属性,则上述示例中的文本将显示这些属性值,而不是来自模型项的type和 age值。 在这种情况下,可以改用model.type 和model.age 来引用这些属性,以确保委托显示来自模型项的属性值。要使此方法生效,您需要在委托中声明一个model 属性(除非您正在使用上下文属性)。
委托还可以使用一个特殊的索引角色,其中包含该项在模型中的索引。请注意,如果该项从模型中移除,此索引将设置为 -1。 如果您绑定到索引角色,请确保逻辑能处理索引为 -1 的情况,即该项已不再有效。(通常该项很快会被销毁,但在某些视图中,可以通过附加属性 `delayRemove ` 延迟委托的销毁。)
请记住,您可以将整数或数组用作模型:
Repeater {
model: ["one", "two", "three"]
Text {
required property string modelData
text: modelData
}
}此类模型会为委托的每个实例提供一个独特的、匿名的数据项。访问该数据项是使用modelData 的主要原因,但其他模型也会提供modelData。
通过模型角色提供的对象有一个名称为空的属性。该匿名属性保存着modelData。此外,通过模型角色提供的对象还有另一个名为modelData 的属性。该属性已弃用,同样保存着modelData。
除了model角色外,还提供了一个modelData角色。modelData角色包含与modelData属性以及通过model角色提供的对象的匿名属性相同的数据。
“model”角色与访问modelData的各种方式之间的区别如下:
- 没有命名角色的模型(例如整数或字符串数组)通过modelData角色提供其数据。在这种情况下,modelData角色不一定包含一个对象。 对于整数模型,该角色将包含一个整数(即当前模型项的索引);对于字符串数组,则包含一个字符串。model角色仍包含一个对象,但其中不包含任何命名角色的属性。不过,model仍包含其通常的modelData和匿名属性。
- 如果模型只有一个命名角色,则modelData角色包含与该命名角色相同的数据。它不一定是对象,也不会像通常那样将命名角色作为命名属性包含其中。在此情况下,model角色仍包含一个对象,该对象将命名角色作为属性,同时还包含modelData和匿名属性。
- 对于具有多个角色的模型,modelData角色仅作为必需属性提供,而非上下文属性。这是为了与旧版本的 Qt 保持向后兼容性。
模型上的匿名属性允许您以简洁的方式编写委托,这些委托会从外部同时接收其模型数据以及应响应的角色名称作为属性。您可以提供一个不包含任何命名角色或仅包含一个命名角色的模型,并将角色设为空字符串。此时,一个仅访问model[role] 的绑定将按预期工作。 您无需为此情况添加特殊代码。
注意: 如果委托包含必填属性,则无法访问model、index 和modelData角色,除非该委托也具有名称匹配的必填属性。
QML 在其内置的 QML 类型集中提供了多种数据模型。此外,还可以使用 Qt C++ 创建模型,然后将其提供给QQmlEngine ,供 QML 组件使用。有关创建这些模型的信息,请参阅《在Qt Quick 视图中使用 C++ 模型》和《创建 QML 类型》这两篇文章。
可通过Repeater 实现模型中项目的定位。
列表模型
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 {
id: delegate
required property string name
required property real cost
Text { text: "Fruit: " + delegate.name }
Text { text: "Cost: $" + delegate.cost }
}
}ListModel 提供了通过 JavaScript 直接操作 `ListModel ` 的方法。在此情况下,插入的第一个项决定了使用该模型的任何视图可用的角色。例如,如果创建了一个空的 `ListModel ` 并通过 JavaScript 填充数据,则视图中显示的角色仅限于首次插入时提供的角色:
ListModel { id: fruitModel }
...
MouseArea {
anchors.fill: parent
onClicked: fruitModel.append({"cost": 5.95, "name":"Pizza"})
}点击MouseArea 时,fruitModel 将包含两个角色:cost和name。即使随后添加了其他角色,使用该模型的视图也只会处理前两个角色。若要重置模型中可用的角色,请调用ListModel::clear()。
XML 模型
XmlListModel 允许从 XML 数据源构建模型。角色通过XmlListModelRole 类型指定。该类型需要被导入。
import QtQml.XmlListModel以下模型包含三个角色:title、link和pubDate:
XmlListModel {
id: feedModel
source: "http://rss.news.yahoo.com/rss/oceania"
query: "/rss/channel/item"
XmlListModelRole { name: "title"; elementName: "title" }
XmlListModelRole { name: "link"; elementName: "link" }
XmlListModelRole { name: "pubDate"; elementName: "pubDate" }
}query 属性指定,XmlListModel 将为XML文档中的每个<item> 生成一个模型项。
RSS 新闻演示展示了如何使用 `XmlListModel ` 来显示 RSS 源。
对象模型
ObjectModel 包含将在视图中使用的可视化项。当在视图中使用ObjectModel 时,该视图无需委托,因为ObjectModel 中已包含可视化委托(项)。
下面的示例在ListView 中放置了三个彩色矩形。
import QtQuick 2.0
import QtQml.Models 2.1
Rectangle {
ObjectModel {
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
}
}整数作为模型
整数可作为包含一定数量类型的模型。在此情况下,该模型不包含任何数据角色。
以下示例创建了一个包含五个元素的ListView :
Item {
width: 200; height: 250
Component {
id: itemDelegate
Text {
required property int index
text: "I am item number: " + index
}
}
ListView {
anchors.fill: parent
model: 5
delegate: itemDelegate
}
}注意: 整数模型中的项目数量上限 为100,000,000。
作为模型的对象实例
对象实例可用于指定仅包含单一对象类型的模型。该对象的属性将作为角色提供。
下面的示例创建了一个包含一个项的列表,显示myText文本的颜色。请注意,这里使用了完全限定的model.color属性,以避免与委托中 Text 类型的color属性发生冲突。
Rectangle {
width: 200; height: 250
Text {
id: myText
text: "Hello"
color: "#dd44ee"
}
Component {
id: myDelegate
Text {
required property var model
text: model.color
}
}
ListView {
anchors.fill: parent
anchors.topMargin: 30
model: myText
delegate: myDelegate
}
}C++ 数据模型
可以在 C++ 中定义模型,然后将其提供给 QML 使用。此机制有助于将现有的 C++ 数据模型或其他复杂数据集暴露给 QML。
如需了解更多信息,请参阅《在Qt Quick 视图中使用C++模型》一文。
数组模型
您可以使用 JavaScript 数组和各种类型的 QML 列表作为模型。列表中的元素将按照上述规则作为 model 和 modelData 提供:整数或字符串等单个数据将作为单个 modelData 提供;JavaScript 对象或 QObject 等结构化数据将作为结构化 model 和 modelData 提供。
如果您将各个模型角色作为必填属性进行请求,它们也会被提供。由于我们无法预先知道数组中会出现哪些对象,因此委托中的任何必填属性都会被填充,可能需要将undefined 强制转换为所需的类型。不过,各个模型角色不会通过 QML 上下文提供。它们会覆盖所有其他上下文属性。
重复器

重复器利用模型中的数据,根据模板创建项目,以便与定位器配合使用。结合使用重复器和定位器是布局大量项目的简便方法。将一个Repeater 项目放置在定位器内部,它会生成项目,由外围的定位器进行排列。
每个重复器通过将模型中的每个数据元素(通过model 属性指定)与模板项(定义为重复器内的子项)相结合,生成若干项。项的总数由模型中的数据量决定。
以下示例展示了一个与 Grid 控件配合使用的 Repeater 控件,用于排列一组 Rectangle 控件。该 Repeater 控件生成了一系列 24 个矩形,供 Grid 控件以 5×5 的布局进行定位。
import QtQuick
Rectangle {
width: 400
height: 400
color: "black"
Grid {
x: 5
y: 5
rows: 5
columns: 5
spacing: 10
Repeater {
model: 24
Rectangle {
id: delegate
required property int index
width: 70
height: 70
color: "lightgreen"
Text {
text: delegate.index
font.pointSize: 30
anchors.centerIn: parent
}
}
}
}
}Repeater 生成的项目数量由其 `count ` 属性控制。无法通过设置该属性来确定要生成的项目数量。相反,如上例所示,我们使用一个整数作为模型。
更多详细信息,请参阅《QML 数据模型》文档。
如果模型是一个字符串列表,则委托还会暴露一个常规的只读modelData 属性,该属性存储该字符串。例如:
|
还可以将委托用作 Repeater 创建的项的模板。这通过delegate 属性进行指定。
更改模型数据
所有相关的视图都具有一个名为delegateModelAccess 的属性,该属性控制您是否以及如何通过委托修改模型数据。 对于大多数用例,应将其设置为`DelegateModel.ReadWrite`。这样可以使您的委托以最灵活的方式修改模型数据。如果不想让委托修改任何模型数据,则可将其设置为 `DelegateModel.ReadOnly`。
下表详细描述了该属性可取的所有值。
| DelegateModel.Qt5ReadWrite | 委托可以为视图提供的任何上下文属性赋值,从而更改相关的模型项。此外,它还可以为作为上下文属性或必填属性提供的model 对象的属性赋值,以达到相同的效果。不过,委托无法通过为视图填充的必填属性赋值来更改模型数据。 若向委托的必填属性赋值,则从模型数据更新该必填属性的绑定关系将失效。这意味着即使模型后续发生变化,该必填属性仍将保留您所赋予的值。DelegateModel.Qt5ReadWrite是delegateModelAccess 的默认值。 |
| DelegateModel.ReadOnly | 委托不能向视图提供的上下文属性赋值,也不能向model 对象的属性赋值。向由视图填充的必填属性赋值不会破坏内部绑定,也不会更改任何模型数据。该必填属性将保留其被赋予的值,直到模型项再次发生变化。 |
| DelegateModel.ReadWrite | 委托可以向视图提供的任何上下文属性赋值,从而更改相关的模型项。此外,它还可以向作为上下文属性或必填属性提供的model 对象的属性赋值,以达到相同的效果。 该委托还可以通过为视图填充的必填属性赋值来更改模型数据。为必填属性赋值会将该值传播到相应的模型项,且不会破坏内部绑定。 |
某些模型是具有独立身份的对象,视图引用这些对象。原始对象是创建委托的唯一真实来源,不会被复制。通过委托进行写入时,此类模型将可见地更新。
其他模型则不具备独立身份,在将其赋值给视图时会被复制。对于此类模型,通过委托进行写入时,仅会更新视图的内部数据,原始模型保持不变。
一般而言,QML 对象类型具有独立身份,而QML 值类型则没有。 因此,ListModel 、任何从QAbstractItemModel 派生的类型,以及对象类型的单个实例或对象类型实例列表,在作为模型传递并通过委托进行写入时都会被更新。然而,当您通过委托更改模型数据时,JavaScript 数组、值类型的列表或简单数字不会收到更新。
此外,在实现您自己的 C++ 模型时,您需要实现setData 方法,以便接收从委托传递过来的任何更新。
假设有一个基于QAbstractItemModel 的C++模型,该模型实现了setData 方法,并被注册为名为EditableModel 的QML类型。此时,可以像这样将数据写入该模型:
ListView {
anchors.fill: parent
model: EditableModel {}
// Make sure that changes to the required property are propagated
delegateModelAccess: DelegateModel.ReadWrite
delegate: TextEdit {
required property string edit
width: ListView.view.width
height: 30
text: edit
Keys.onReturnPressed: edit = text
}
}您还可以通过如下方式操作model 对象来更改模型数据:
ListView {
anchors.fill: parent
model: EditableModel {}
delegate: TextEdit {
required property QtObject model
width: ListView.view.width
height: 30
text: model.edit
Keys.onReturnPressed: model.edit = text
}
}注意: edit 角色等 同于Qt::EditRole 。有关内置角色名称,请参阅roleNames()。不过,实际应用中的模型通常会注册自定义角色。
如需了解更多信息,请参阅《在Qt Quick 视图中使用C++模型》一文。
使用转换
过渡效果可用于对添加到定位器中、在定位器内移动或从定位器中移除的项目进行动画处理。
用于添加项的过渡效果既适用于作为定位器组成部分创建的项,也适用于被重新父级化为定位器子项的项。
移除项的过渡效果既适用于定位器内被删除的项,也适用于从定位器中移除并被赋予文档中新的父项的项。
注意:将 项的不透明度设置为 零并不会使其从定位器中消失。可以通过更改可见性属性来移除并重新添加这些项。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.
