Qt Quick中的模型和视图

如何在Qt Quick中显示和格式化数据

大多数应用程序需要格式化数据并显示数据。Qt Quick 有模型视图委托的概念来显示数据。它们将数据的可视化模块化,以便开发者或设计师能够控制数据的不同方面。开发者可以用很少的更改将列表视图替换为网格视图。同样,将数据的实例封装在委托中,允许开发者决定如何呈现或处理数据。

../_images/modelview-overview.png
  • 模型 - 包含数据及其结构。有几种QML类型用于创建模型。

  • 视图 - 一个显示数据的容器。视图可能以列表或网格的形式显示数据。

  • Delegate - 决定数据在视图中应如何显示。委托获取模型中的每个数据单元并将其封装。数据可以通过委托访问。委托还可以将数据写回可编辑的模型中(例如,在TextField的onAccepted Handler中)。

要可视化数据,请将视图的model属性绑定到一个模型,并将delegate属性绑定到一个组件或其他兼容类型。

使用视图显示数据

视图是项目集合的容器。它们功能丰富,并且可以自定义以满足样式或行为要求。

在Qt Quick图形类型的基本集合中提供了一组标准视图:

  • ListView - 将项目排列在水平或垂直列表中

  • GridView - 在可用空间内以网格形式排列项目

  • PathView - 在路径上排列项目

  • TableView - 将来自QAbstractTableModel的数据排列在表格中

  • TreeView - 将来自 QAbstractItemModel 的数据排列成树状结构

这些类型具有各自独特的属性和行为。访问它们各自的文档以获取更多信息。

此外,Qt Quick Controls 包含一些根据应用程序样式进行样式化的额外视图和委托,例如 HorizontalHeaderView 和 VerticalHeaderView。

装饰视图

视图允许通过装饰属性(如headerfootersection属性)进行视觉定制。通过将一个对象(通常是另一个视觉对象)绑定到这些属性,视图可以被装饰。页脚可能包括一个显示边框的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
    }
}

俱乐部可以通过将视觉对象绑定到headerfooter属性来装饰成员列表。视觉对象可以内联定义,也可以在另一个文件中定义,或者在组件类型中定义。

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"}
}
../_images/listview-decorations.png

鼠标和触摸处理

视图处理其内容的拖动和轻弹,但它们不处理与各个委托的触摸交互。为了使委托能够对触摸输入做出反应,例如设置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
            }
        }
    }
}
../_images/listview-section.png

查看委托

视图需要一个委托来视觉上表示列表中的项目。视图将根据委托定义的模板可视化每个项目列表。模型中的项目可以通过index属性以及项目的属性来访问。

Component {
    id: petdelegate
    Text {
        id: label
        font.pixelSize: 24
        text: index === 0 ? type + " (default)" : type

        required property int index
        required property string type
    }
}
../_images/listview-setup.png

视图委托的定位

视图的类型将决定项目如何定位。ListView会根据orientation将项目定位在一条直线上,而GridView可以将它们布局在一个二维网格中。建议不要直接在xy上绑定,因为视图的布局行为总是优先于任何位置绑定。

从委托访问视图和模型

委托绑定的列表视图可以通过委托中的ListView.view属性访问。同样,GridViewGridView.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
    }
}

模型

数据通过命名数据角色提供给委托,委托可以绑定这些角色。这里是一个具有两个角色typeage的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类型具有(非必需的)typeage属性,上述示例中的文本将显示这些属性值,而不是模型项中的typeage值。在这种情况下,可以将属性引用为model.typemodel.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] 的绑定将按预期工作。您不必为此情况添加特殊代码。

注意

如果委托包含必需的属性,则无法访问modelindexmodelData角色,除非它还具有匹配名称的必需属性。

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将有两个角色,costname。即使后续添加了更多角色,视图中使用该模型时只会处理前两个角色。要重置模型中可用的角色,请调用ListModel::clear()。

XML 模型

XmlListModel 允许从 XML 数据源构建模型。角色通过 XmlListModelRole 类型指定。需要导入该类型。

import QtQml.XmlListModel

以下模型有三个角色,titlelinkpubDate

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-index1

重复器从模板创建项目,用于与定位器一起使用,使用来自模型的数据。结合重复器和定位器是布局大量项目的简单方法。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
        }
    }
}
../_images/repeater-modeldata.png

也可以使用委托作为由Repeater创建的项目的模板。这是通过使用delegate属性来指定的。

更改模型数据

要更改模型数据,您可以将更新后的值分配给model属性。QML ListModel默认是可编辑的,而C++模型必须实现setData()才能变为可编辑。整数和JavaScript数组模型是只读的。

假设一个基于 QAbstractItemModel 的 C++ 模型实现了 setData 方法,并被注册为名为 EditableModel 的 QML 类型。然后可以像这样将数据写入模型:

注意

edit 角色等同于 Qt::EditRole。有关内置角色名称,请参见 roleNames()。然而,实际生活中的模型通常会注册自定义角色。

注意

如果模型角色绑定到一个必需的属性,分配给该属性将不会修改模型。相反,它会破坏与模型的绑定(就像分配给任何其他属性会破坏现有绑定一样)。如果你想使用必需的属性并更改模型数据,使模型也成为必需的属性,并分配给model.propertyName

欲了解更多信息,请访问使用C++模型与Qt Quick视图文章。

使用过渡

过渡效果可以用于动画化那些被添加到定位器、在定位器内移动或从定位器中移除的项目。

添加项目的过渡适用于作为定位器的一部分创建的项目,以及那些被重新定位为定位器子项的项目。

移除项目的过渡适用于在定位器内被删除的项目,以及从定位器中移除并在文档中赋予新父项的项目。

注意

将项目的不透明度更改为零不会导致它们从定位器中消失。可以通过更改可见属性来移除和重新添加它们。