模型和视图Qt Quick
大多数应用程序都需要格式化数据并显示数据。Qt Quick 有模型、视图和委托的概念来显示数据。它们将数据可视化模块化,以便让开发人员或设计人员控制数据的不同方面。开发人员可以将列表视图与网格视图互换,而对数据的改动很小。同样,将数据实例封装在委托中,开发人员就可以决定如何展示或处理数据。
- 模型--包含数据及其结构。有几种 QML 类型可用于创建模型。
- 视图- 显示数据的容器。视图可在列表或网格中显示数据。
- 委托--决定数据在视图中的显示方式。委托将模型中的每个数据单元封装起来。数据可通过委托进行访问。委托还可以将数据写回可编辑模型(例如,在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 列表视图的内容可分为多个部分,相关的列表项会根据其部分进行标注。此外,还可以用代表来装饰这些部分。
列表可包含一个显示人名和所属团队的列表。
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 } }
视图委托的定位
视图的类型将决定项目的定位方式。根据orientation ,ListView 将以直线方式定位项目,而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 } }
模型
数据是通过命名的数据角色提供给委托的,委托可以绑定这些角色。下面是一个带有两个角色(类型和年龄)的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 类型具有(非必须的)类型或年龄属性,则上例中的文本将显示这些属性值,而不是模型项中的类型和年龄值。在这种情况下,可以将属性引用为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。
除模型角色外,还提供了一个modelData角色。modelData角色持有与通过模型角色提供的对象的modelData属性和匿名属性相同的数据。
模型角色与访问modelData的各种方法之间的区别如下:
- 没有命名角色(如整数或字符串数组)的模型,其数据通过modelData角色提供。在这种情况下,modelData角色不一定包含一个对象。如果是整数模型,它将包含一个整数(当前模型项的索引)。如果是字符串数组,则包含一个字符串。模型角色仍然包含一个对象,但不包含命名角色的任何属性。
- 如果模型只有一个命名角色,那么modelData角色包含的数据与命名角色相同。它不一定是一个对象,也不像通常那样包含命名角色的命名属性。在这种情况下,模型角色仍然包含一个以命名角色为属性的对象,以及modelData和匿名属性。
- 对于具有多个角色的模型,modelData角色只作为必填属性而不是上下文属性提供。这是为了向后兼容旧版本的 Qt。
通过模型上的匿名属性,您可以简洁地编写委托,从外部接收其模型数据和角色名称作为属性。您可以提供一个没有角色名称或只有一个角色名称的模型,并提供一个空字符串作为角色。这样,只需访问model[role]
的绑定就能实现您的期望。您不必为这种情况添加特殊代码。
注意: 如果委托包含必填属性,则无法访问模型、索引和modelData角色,除非委托也包含名称匹配的必填属性。
QML 在内置的 QML 类型集中提供了多种类型的数据模型。此外,模型可用 Qt C++ 创建,然后提供给QQmlEngine 供 QML 组件使用。有关创建这些模型的信息,请访问使用 C++ 模型与Qt Quick Views和创建 QML 类型的文章。
使用Repeater.NET 文件可实现模型中项的定位。
列表模型
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 delegate 绑定:
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 直接操作 的方法。在这种情况下,插入的第一个项目决定了使用该模型的任何视图可用的角色。例如,如果通过 JavaScript 创建并填充了一个空的 ,则第一个插入项提供的角色将是视图中显示的唯一角色:ListModel ListModel
ListModel { id: fruitModel } ... MouseArea { anchors.fill: parent onClicked: fruitModel.append({"cost": 5.95, "name":"Pizza"}) }
当点击MouseArea 时,fruitModel
将有两个角色:成本和名称。即使添加了后续角色,使用模型的视图也只会处理前两个角色。要重置模型中可用的角色,请调用ListModel::clear() 。
XML 模型
XmlListModel 允许从 XML 数据源构建模型。角色是通过 类型指定的。该类型需要导入。XmlListModelRole
import QtQml.XmlListModel
下面的模型有三个角色:标题、链接和发布日期:
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文本的颜色。请注意,为了避免与委托中文本类型的color属性冲突,使用了完全限定的model.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。这种机制对于向 QML 公开现有 C++ 数据模型或其他复杂数据集非常有用。
有关信息,请访问使用 C++ 模型与Qt Quick Views文章。
数组模型
您可以使用 JavaScript 数组和各种 QML 列表作为模型。列表中的元素将按照上述规则作为模型和 modelData 提供:整数或字符串等单数数据将作为单数 modelData 提供。JavaScript 对象或 QObjects 等结构化数据则作为结构化模型和 modelData 提供。
如果您要求将单个模型角色作为必备属性,我们也会提供这些角色。由于我们无法事先知道数组中会出现哪些对象,因此委托中的任何必填属性都将被填充,可能会通过胁迫undefined
到必填类型。不过,单个模型角色不能通过 QML 上下文提供。它们会影响所有其他上下文属性。
中继器
中继器使用模型中的数据从模板中创建项目,以便与定位器一起使用。将中继器和定位器结合起来,是布局大量项目的一种简便方法。Repeater 项目放置在定位器内,并生成由外层定位器排列的项目。
使用model 属性指定的模型中的每个数据元素与作为 Repeater 中子项定义的模板项相结合,每个 Repeater 都会创建若干个项。项目总数由模型中的数据量决定。
下面的示例显示了与网格项一起使用的中继器,用于排列一组矩形项。中继器项创建了一系列 24 个矩形,供网格项以 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 } } } } }
中继器创建的项数由count 属性决定。无法通过设置该属性来确定要创建的项数。相反,在上面的示例中,我们使用整数作为模型。
更多详情,请参阅QML 数据模型文档。
如果模型是字符串列表,delegate 也暴露给通常的只读modelData
属性,该属性保存字符串。例如
也可以使用委托作为 Repeater 创建的项的模板。这可以通过delegate 属性来指定。
更改模型数据
要更改模型数据,可将更新值分配给model
属性。QMLListModel 默认是可编辑的,而 C++ 模型必须实现 setData() 才能成为可编辑的。整数和 JavaScript 数组模型是只读的。
假设一个实现了setData 方法的基于QAbstractItemModel 的 C++ 模型被注册为名为EditableModel
的 QML 类型。数据就可以像这样写入模型:
ListView { anchors.fill: parent model: EditableModel {} delegate: TextEdit { required property var model width: ListView.view.width height: 30 text: model.edit Keys.onReturnPressed: model.edit = text } }
注意: edit
角色等于Qt::EditRole 。内置角色名称请参见roleNames() 。不过,现实生活中的模型通常会注册自定义角色。
注意: 如果模型角色绑定了一个必填属性,对该属性赋值将不会修改模型。相反,它将破坏与模型的绑定(就像对任何其他属性赋值都会破坏现有绑定一样)。如果您想使用必填属性并更改模型数据,请将 model 也设为必填属性并赋值给model.propertyName。
如需了解更多信息,请访问 Qt Quick Views 中的 "使用 C++ 模型"一文。
使用过渡
过渡可用于将添加到定位器、在定位器中移动或从定位器中移除的项目制作成动画。
用于添加项目的转场适用于作为定位器的一部分创建的项目,也适用于那些被重新分配成为定位器子代的项目。
移除项目的转换适用于定位器中被删除的项目,以及从定位器中移除并在文档中获得新父节点的项目。
注意: 将项目的不透明度变为零不会导致它们从定位器中消失。它们可以通过更改可见属性进行删除和重新添加。
© 2025 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.