QML DelegateChooser:按角色选择委托与按索引选择委托

ListView 列表组件中的 delegate(委托)只能放入一个组件,这导致所有的行外观一致,而实际情况我们需要图文混排的列表、个别特殊行要自定义。

DelegateChooser 能解决这个需求,它把若干条 DelegateChoice 攒在一起,按数据取值或行位置挑选一个委托出来。这篇讲最基础的两种挑法:按角色取值匹配、按行索引匹配。

先认识 DelegateChooser

DelegateChooser 本身是一个特殊的组件,专门用在视图需要 delegate 的位置。它内部装着一串 DelegateChoice,每条等于"一个委托 + 一组命中条件"。

DelegateChooser 自己只有两个属性:

  • role(角色) ------ 按哪一列的数据来判断。只按列号挑委托时不需要它。
  • choices(选择列表) ------ 一串 DelegateChoice。它是默认属性,所以 DelegateChoice 直接写成子项就行,不必显式包进 choices。

DelegateChoice 能写条件的一共三个属性,外加一个委托:

  • roleValue(角色取值) ------ 和 role 指的那一列数据比对。
  • index(行号) ------ 匹配第几行。它和 row 互为别名,两个都写属于未定义行为,只写一个。
  • column(列号) ------ 匹配第几列,只在带列的模型上有意义。
  • delegate(委托) ------ 命中之后用哪个组件。

规则也说清楚。看 Qt 源码里那段判定:三个条件之间是与的关系,写了哪个就要求哪个满足,没写的视为不限制。三个都没写的 DelegateChoice 会被直接判定为命中,所以它就是兜底项。挑选时按声明顺序从上往下扫,第一个命中即停止;一条都不命中时,视图拿到的委托是空的,那一行什么都不显示。兜底项通常不能省,原因就在这。

写法上还有个自由度:DelegateChooser 既可以直接写在视图的 delegate: 后面(示例就是这种),也可以先声明成一个带 id 的组件、再在 delegate: 里引用它。后者在同一个 chooser 要被多处复用时省事。

Demo 1 基本用法

列表四条数据,靠 type 这一列区分:如果是 image 的行渲染成居中图片,如果是 text 的行渲染成一行彩色文字。两种行高度不同,各自在自己的委托里定。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.qmlmodels

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 16
        spacing: 12

        // ... 省略标题组件 TitleSeparator ...

        // 内容区:垂直方向填满剩余空间
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#fafafa"
            radius: 8
            border.color: "#e0e0e0"
            border.width: 1

            ListView {
                anchors.fill: parent
                anchors.margins: 8
                spacing: 8
                clip: true
                ScrollBar.vertical: ScrollBar {}

                model: ListModel {
                    ListElement { type: "image"; source: "qrc:/qt_logo.png" }
                    ListElement { type: "text"; content: "这是一段文本内容"; color: "blue" }
                    ListElement { type: "image"; source: "qrc:/qt_logo.png" }
                    ListElement { type: "text"; content: "又是一段文本"; color: "red" }
                }

                delegate: DelegateChooser {
                    role: "type"

                    DelegateChoice {
                        roleValue: "image"
                        delegate: Rectangle {
                            width: ListView.view.width
                            height: 76
                            color: "white"
                            radius: 6
                            border.color: "#e0e0e0"
                            border.width: 1

                            Image {
                                anchors.centerIn: parent
                                width: 60
                                height: 60
                                source: model.source
                                fillMode: Image.PreserveAspectFit
                            }
                        }
                    }

                    DelegateChoice {
                        roleValue: "text"
                        delegate: Rectangle {
                            width: ListView.view.width
                            height: 42
                            color: "white"
                            radius: 6
                            border.color: "#e0e0e0"
                            border.width: 1

                            Text {
                                anchors.centerIn: parent
                                text: model.content
                                font.pixelSize: 15
                                color: model.color
                            }
                        }
                    }
                }
            }
        }
    }
}

关键逻辑解析

分发逻辑就一句:role 写在 DelegateChooser 上声明"按哪一列判断",每条 DelegateChoice 用自己的 roleValue 声明"取什么值时用我",按声明顺序从上往下扫、第一条命中即生效,所以 roleValue 要和模型里的字面量完全一致。

委托里照旧用 model.xxx 读本行数据,行高由委托自己定------分发只决定用哪份委托,不碰数据怎么读。有个容易想当然的坑:roleValue 只做单值比较,写成数组不会命中任何行,想匹配多个取值就写多条 DelegateChoice

Demo 2 索引条件

差异不来自数据,而来自位置:列表第 0 项要用特殊样式标出来。DelegateChoiceindex 正是干这个的,命中指定行号就用它的委托,其余行走后面那条不带条件的兜底委托。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.qmlmodels

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 16
        spacing: 12

        // ... 省略标题组件 TitleSeparator ...

        // 内容区:垂直方向填满剩余空间
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#fafafa"
            radius: 8
            border.color: "#e0e0e0"
            border.width: 1

            ListView {
                anchors.fill: parent
                anchors.margins: 8
                spacing: 8
                clip: true
                ScrollBar.vertical: ScrollBar {}

                model: ListModel {
                    ListElement { value: 100; description: "特殊项目" }
                    ListElement { value: 200; description: "常规项目" }
                    ListElement { value: 300; description: "常规项目" }
                    ListElement { value: 400; description: "常规项目" }
                }

                delegate: DelegateChooser {
                    DelegateChoice {
                        index: 0
                        delegate: Rectangle {
                            width: ListView.view.width
                            height: 60
                            color: "#2196F3"
                            radius: 8

                            Text {
                                anchors.centerIn: parent
                                text: model.description
                                color: "white"
                                font {
                                    bold: true
                                    pixelSize: 16
                                }
                            }
                        }
                    }

                    DelegateChoice {
                        delegate: Rectangle {
                            width: ListView.view.width
                            height: 46
                            color: "white"
                            radius: 6
                            border.color: "#e0e0e0"
                            border.width: 1

                            RowLayout {
                                anchors {
                                    fill: parent
                                    margins: 10
                                }
                                spacing: 10

                                Text {
                                    text: model.description
                                    font.pixelSize: 14
                                    Layout.fillWidth: true
                                }
                                Text {
                                    text: model.value
                                    font {
                                        pixelSize: 14
                                        bold: true
                                    }
                                    color: "#1976D2"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

关键逻辑解析

按索引挑选:DelegateChoice { index: 0 } 只对第 0 行生效,渲染成特殊卡片,其余行走最后那条不带任何条件的兜底委托。

关键在于顺序------声明顺序就是优先级,命中即停止,所以特例写前面、兜底写最后;兜底项就是"三个条件一个都没写"的那条,源码里它会被直接判定为命中。

index 只适合"第几行特殊"这类位置需求,真要"最后一行特殊"得先知道行数,不如按角色判断或给那行数据加一个标记字段。

role 的两个细节

模型类型决定"什么时候重新挑委托"

文档里写明:模型如果是 QAbstractItemModel 系(ListModel 就属于这类),角色值变化时 DelegateChooser 会重新评估一次选择。所以把某行的 typetext 改成 image,那一行会当场换一份委托。换成 JS 数组这类普通模型,选择只在创建那一行时做一次,之后再改数据不会重挑。

行上只有 modelData 时,role 指的名字会去 modelData 里找

源码里按角色取值失败后会退回读 modelData,如果它是个对象或 map,就按 role 给的名字取字段。也就是说模型写成 [{ type: "image", ... }] 这样的 JS 对象数组时,role: "type" 照样能用。

两种匹配方式对比

方式 判定依据 写在哪 典型场景
按角色 某一列的数据取值 DelegateChooser { role }DelegateChoice { roleValue } 图文混排、不同类型的消息、不同控件的设置项
按索引 行号 DelegateChoice { index } 首项置顶高亮、某一行特殊样式
兜底 以上都不命中 条件全都不写的 DelegateChoice 常规行的默认样式

如何选择看差异从哪来

差异跟着数据走就用角色匹配,数据变了视觉自己跟着变,不用维护行号;差异只跟位置有关(列表头、置顶项),才用索引。

两个条件也能写在同一 DelegateChoice 上,表示"第 N 行里取值等于 X 的那一条",条件叠加,不会变成"或"。


已验证环境

相关推荐
Cx330❀2 小时前
Qt 常用 UI 控件详解:从QPushButton到QLabel 核心用法解析
qt·ui·图形渲染
实心儿儿1 天前
Qt — 信号和槽
qt
江湖人称菠萝包1 天前
【Qt】《Qt 5.9 C++开发指南》笔记-Chapter8-绘图
笔记·qt·qt5
西西弗Sisyphus1 天前
Qt 实现一个 水波进度球
c++·qt·c
RAN11426760082 天前
1.3第一个程序的介绍
开发语言·qt
实心儿儿2 天前
Qt — 命名方式、坐标系
qt
小小龙学IT2 天前
Qt QPainter 2D 绘图系统深度解析:从画笔到渲染引擎适
c++·qt
深兰科技2 天前
深兰科技受邀参与第二届中国(南宁)—东盟人工智能场景应用对接会,深化AI国际合作
人工智能·qt·r语言·scala·symfony·智能机器人·深兰科技
实心儿儿2 天前
Qt — 对象树
qt