Qt Quick中的模型和视图¶
如何在Qt Quick中显示和格式化数据
大多数应用程序需要格式化数据并显示数据。Qt Quick 有模型、视图和委托的概念来显示数据。它们将数据的可视化模块化,以便开发者或设计师能够控制数据的不同方面。开发者可以用很少的更改将列表视图替换为网格视图。同样,将数据的实例封装在委托中,允许开发者决定如何呈现或处理数据。
![]()
模型 - 包含数据及其结构。有几种QML类型用于创建模型。
视图 - 一个显示数据的容器。视图可能以列表或网格的形式显示数据。
Delegate - 决定数据在视图中应如何显示。委托获取模型中的每个数据单元并将其封装。数据可以通过委托访问。委托还可以将数据写回可编辑的模型中(例如,在TextField的onAccepted Handler中)。
要可视化数据,请将视图的model
属性绑定到一个模型,并将delegate
属性绑定到一个组件或其他兼容类型。
使用视图显示数据¶
视图是项目集合的容器。它们功能丰富,并且可以自定义以满足样式或行为要求。
在Qt Quick图形类型的基本集合中提供了一组标准视图:
这些类型具有各自独特的属性和行为。访问它们各自的文档以获取更多信息。
此外,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 } }
俱乐部可以通过将视觉对象绑定到header
和footer
属性来装饰成员列表。视觉对象可以内联定义,也可以在另一个文件中定义,或者在组件类型中定义。
ListView { anchors.fill: parent clip: true model: nameModel delegate: nameDelegate header: bannercomponent footer: Rectangle { width: parent.width; height: 30; gradient: clubcolors } highlight: Rectangle { width: parent.width 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 的内容可以分组为 部分,其中相关的列表项根据其部分进行标记。此外,这些部分可以使用 delegates 进行装饰。
列表可能包含一个列表,指示人们的姓名以及该人所属的团队。
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 { text: name; font.pixelSize: 24 anchors.left: parent.left anchors.leftMargin: 2 } }
ListView 类型具有 section
附加属性,可以将相邻和相关类型组合成一个部分。section.property
确定使用哪个列表类型属性作为部分。section.criteria
可以决定部分名称的显示方式,而 section.delegate
类似于视图的 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 Text { text: " Fruit: " + name; color: fruit.ListView.view.fruit_color } Text { text: " Cost: $" + 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上下文传递数据会降低组件的可重用性。
如果模型的属性和委托的属性之间存在命名冲突,可以使用限定的model名称来访问角色。例如,如果Text类型具有(非必需的)type或age属性,上述示例中的文本将显示这些属性值,而不是模型项中的type和age值。在这种情况下,可以将属性引用为model.type
和model.age
,以确保委托显示模型项中的属性值。为此,您需要在委托中要求一个model
属性(除非您使用的是上下文属性)。
委托还可以使用一个特殊的index角色,该角色包含模型中项目的索引。请注意,如果项目从模型中移除,此索引将设置为-1。如果你绑定到索引角色,请确保逻辑考虑到索引可能为-1的情况,即项目不再有效。(通常项目很快就会销毁,但在某些视图中,可以通过delayRemove
附加属性延迟委托的销毁。)
记住,你可以使用整数或数组作为模型:
此类模型为每个委托实例提供一个单一的、匿名的数据片段。访问这个数据片段是使用modelData的主要原因,但其他模型也提供modelData。
通过model角色提供的对象具有一个名称为空的属性。这个匿名属性包含modelData。此外,通过model角色提供的对象还有另一个名为modelData的属性。此属性已被弃用,并且也包含modelData。
除了model角色外,还提供了一个modelData角色。modelData角色持有与modelData属性以及通过model角色提供的对象的匿名属性相同的数据。
model 角色与访问 modelData 的各种方法之间的区别如下:
没有命名角色的模型(例如整数或字符串数组)通过modelData角色提供数据。在这种情况下,modelData角色不一定包含一个对象。对于整数模型,它将包含一个整数(当前模型项的索引)。对于字符串数组,它将包含一个字符串。model角色仍然包含一个对象,但没有命名角色的任何属性。model仍然包含其通常的modelData和匿名属性。
如果模型只有一个命名角色,modelData 角色包含与命名角色相同的数据。它不一定是一个对象,也不像通常那样包含命名角色作为命名属性。model 角色仍然包含一个对象,该对象具有命名角色作为属性,以及在这种情况下modelData 和匿名属性。
对于具有多个角色的模型,modelData 角色仅作为必需属性提供,而不是作为上下文属性。这是由于与旧版本 Qt 的向后兼容性。
model 上的匿名属性允许您清晰地编写委托,这些委托从外部接收其模型数据和它们应响应的角色名称作为属性。您可以提供一个没有或只有一个命名角色的模型,并将角色设置为空字符串。然后,一个简单地访问 model[role]
的绑定将按预期工作。您不必为此情况添加特殊代码。
注意
如果委托包含必需的属性,则无法访问model、index和modelData角色,除非它还具有匹配名称的必需属性。
QML 在内置的 QML 类型集中提供了几种数据模型。此外,可以使用 Qt C++ 创建模型,然后提供给 QQmlEngine 供 QML 组件使用。有关创建这些模型的信息,请访问 使用 C++ 模型与 Qt Quick 视图 和创建 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 } }
上述模型有两个角色,名称和成本。这些可以被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:
query
属性指定 XmlListModel 为 XML 文档中的每个
生成一个模型项。
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:
注意
整数模型中的项目数量限制为100,000,000。
对象实例作为模型¶
对象实例可用于指定具有单一对象类型的模型。对象的属性作为角色提供。
下面的示例创建了一个包含一个项目的列表,显示了myText文本的颜色。请注意使用完全限定的model.color属性,以避免与委托中Text类型的color属性冲突。
C++ 数据模型¶
模型可以在C++中定义,然后提供给QML使用。这种机制对于将现有的C++数据模型或其他复杂的数据集暴露给QML非常有用。
如需了解更多信息,请访问使用C++模型与Qt Quick视图文章。
数组模型¶
您可以使用JavaScript数组和各种QML列表作为模型。列表的元素将根据上述规则作为model和modelData提供:像整数或字符串这样的单一数据将作为单一的modelData提供。像JavaScript对象或QObjects这样的结构化数据将作为结构化的model和modelData提供。
如果您将它们作为必需的属性请求,则各个模型角色也可用。由于我们无法预先知道数组中会出现什么对象,因此将填充委托中的任何必需属性,可能会将undefined
强制转换为所需的类型。然而,各个模型角色不会通过QML上下文提供。它们会遮蔽所有其他上下文属性。
中继器¶
重复器从模板创建项目,用于与定位器一起使用,使用来自模型的数据。结合重复器和定位器是布局大量项目的简单方法。Repeater 项目放置在定位器内,并生成由封闭定位器排列的项目。
每个Repeater通过将来自模型的数据的每个元素(使用model属性指定)与模板项(定义为Repeater内的子项)结合来创建多个项目。项目的总数由模型中的数据量决定。
以下示例展示了与Grid项目一起使用的repeater,用于排列一组Rectangle项目。Repeater项目为Grid项目创建了24个矩形,以5x5的排列方式进行定位。
import QtQuick Rectangle { width: 400; height: 400; color: "black" Grid { x: 5; y: 5 rows: 5; columns: 5; spacing: 10 Repeater { model: 24 Rectangle { width: 70; height: 70 color: "lightgreen" Text { text: index font.pointSize: 30 anchors.centerIn: parent } } } } }
由Repeater创建的项的数量由其count属性持有。无法通过设置此属性来确定要创建的项的数量。相反,如上例所示,我们使用一个整数作为模型。
更多详情,请参阅QML数据模型文档。
如果模型是一个字符串列表,委托也会暴露给通常的只读 modelData
属性,该属性保存字符串。例如:
列 { 重复器 { 模型: ["apples", "oranges", "pears"] 文本 { 必需 属性 字符串 modelData 文本: "数据: " + modelData } } }![]()
也可以使用委托作为由Repeater创建的项目的模板。这是通过使用delegate属性来指定的。
更改模型数据¶
要更改模型数据,您可以将更新后的值分配给model
属性。QML ListModel默认是可编辑的,而C++模型必须实现setData()才能变为可编辑。整数和JavaScript数组模型是只读的。
假设一个基于 QAbstractItemModel 的 C++ 模型实现了 setData 方法,并被注册为名为 EditableModel
的 QML 类型。然后可以像这样将数据写入模型:
注意
edit
角色等同于 Qt::EditRole。有关内置角色名称,请参见 roleNames()。然而,实际生活中的模型通常会注册自定义角色。
注意
如果模型角色绑定到一个必需的属性,分配给该属性将不会修改模型。相反,它会破坏与模型的绑定(就像分配给任何其他属性会破坏现有绑定一样)。如果你想使用必需的属性并更改模型数据,使模型也成为必需的属性,并分配给model.propertyName。
欲了解更多信息,请访问使用C++模型与Qt Quick视图文章。
使用过渡¶
过渡效果可以用于动画化那些被添加到定位器、在定位器内移动或从定位器中移除的项目。
添加项目的过渡适用于作为定位器的一部分创建的项目,以及那些被重新定位为定位器子项的项目。
移除项目的过渡适用于在定位器内被删除的项目,以及从定位器中移除并在文档中赋予新父项的项目。
注意
将项目的不透明度更改为零不会导致它们从定位器中消失。可以通过更改可见属性来移除和重新添加它们。