Repeater 是 Qt Quick 里负责批量生成的组件。这篇介绍三种最轻量的模型写法,直接给model一个整数、一个 JavaScript 对象数组、或者一个字符串数组。
先认识 Repeater 的三个要点
model(模型,定义数据) ------ 告诉 Repeater "要做几份、每份的数据是什么"。它接受的东西很杂,整数、JS 数组、字符串列表、ListModel、C++ 模型都可以,这篇只介绍前三种不需要数据结构的。
delegate(委托,定义外观) ------ 每一份长什么样。Repeater 会为模型里每一项实例化一份委托,按顺序放进父项的 data 中。
委托里能直接用的两个名字 ------ index 是当前第几项(从 0 开始),modelData 是这一项的数据。整数模型只有 index 有意义,数组模型主要靠 modelData 获取内容。
还有个细节:Repeater 自带一个只读属性 count,表示当前委托个数。所以自己定义的"数量"属性千万别叫 count,会被遮蔽。
委托本身有两种写法。量少又只有这一个地方用,就把 Rectangle 直接内联写在委托后面;结构稍微复杂、或者想换个外观复用,可以抽成独立的 .qml 组件文件,委托写成 ItemRect { ... } ,只把数据传进去。
Demo 1 整数作为模型

model 直接写整数,界面上就出现对应的行数,每行显示 Item0、Item1 这样的文字,序号来自委托里的 index。
演示代码
qml
import QtQuick
import QtQuick.Layouts
FadeInAnimation {
// 整数模型:改这个值,Repeater 就会增删委托实例
// 注意不能命名为 count ------ Repeater 自带只读 count(当前项数),会把它遮蔽
property int modelCount: 4
readonly property int maxCount: 8
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 12
// ... 省略标题组件 TitleSeparator ...
CountBar {
info: "model: " + modelCount
canIncrease: modelCount < maxCount
canDecrease: modelCount > 0
onIncreaseRequested: modelCount++
onDecreaseRequested: modelCount--
}
Repeater {
model: modelCount
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 25
radius: 6
color: "#f5f5f5"
border.color: "#ccc"
border.width: 1
Text {
anchors.centerIn: parent
text: "Item" + index
color: "#333"
font.pointSize: 10
font.bold: true
}
}
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
整数模型是最省的写法:model: modelCount 是属性绑定而不是一次性快照,整数一变 Repeater 就自动补删委托,所以"增加 / 减少"只需改那个数,行数跟着变。委托里只拿得到 index,文字用 "Item" + index 拼。两个小坑:外层数量属性不能叫 count,Repeater 自带只读的 count(当前委托个数),同名会把它遮蔽;Repeater 自己不是可视元素、也不参与布局,它只把委托塞进父项的 data,所以委托直接写 Layout.fillWidth 这类附加属性、交给外层布局统一排版。
Demo 2 JavaScript 数组

model 使用了对象数组,每个元素带名字、颜色、描述三个字段。
演示代码
qml
import QtQuick
import QtQuick.Layouts
FadeInAnimation {
property var itemList: [
{ name: "Item1", color: "#3498db", des: "modelData.des" },
{ name: "Item2", color: "#e74c3c", des: "modelData.des" },
{ name: "Item3", color: "#2ecc71", des: "modelData.des" },
{ name: "Item4", color: "#f39c12", des: "modelData.des" }
]
readonly property var colors: ["#3498db", "#e74c3c", "#2ecc71", "#f39c12"]
readonly property int maxCount: 8
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 12
// ... 省略标题组件 TitleSeparator ...
CountBar {
info: "数组长度: " + itemList.length
canIncrease: itemList.length < maxCount
canDecrease: itemList.length > 0
// var 属性只有「重新赋值」才会发变更通知,push / splice 原地改数组不会刷新
onIncreaseRequested: {
var next = itemList.length
itemList = itemList.concat({
name: "Item" + (next + 1),
color: colors[next % colors.length],
des: "modelData.des"
})
}
onDecreaseRequested: itemList = itemList.slice(0, -1)
}
Repeater {
model: itemList
delegate: ItemRect {
itemColor: modelData.color
itemText: modelData.name
itemDes: modelData.des
}
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
对象数组的字段通过 modelData.字段 取------modelData 就是当前那一项的对象,这是最轻量的结构化数据,不需要定义任何模型类。真正的重点在增删:itemList 是 var 属性,Qt 只在整个属性被重新赋值 时才发变更通知,push / splice 原地改数组界面不会刷新,所以"增加"用 concat 拼出新数组、"减少"用 slice 切出新数组再赋回去。颜色用 colors[next % colors.length] 按下标循环取,加到第几项都不会取到空值。
Demo 3 字符串数组

model 使用一个纯字符串数组,委托里 modelData 直接等于那个字符串,不需要使用字段名属性。
演示代码
qml
import QtQuick
import QtQuick.Layouts
FadeInAnimation {
// 字符串数组:元素本身就是 modelData
property var items: ["Item1", "Item2", "Item3", "Item4"]
readonly property int maxCount: 8
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 12
// ... 省略标题组件 TitleSeparator ...
CountBar {
info: "数组长度: " + items.length
canIncrease: items.length < maxCount
canDecrease: items.length > 0
// var 属性只有「重新赋值」才会发变更通知,push / pop 原地改数组不会刷新
onIncreaseRequested: items = items.concat("Item" + (items.length + 1))
onDecreaseRequested: items = items.slice(0, -1)
}
Repeater {
model: items
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 25
radius: 6
color: "#f5f5f5"
border.color: "#ccc"
border.width: 1
Text {
anchors.centerIn: parent
color: "#333"
font.pointSize: 10
font.bold: true
text: modelData
}
}
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
纯字符串数组时 modelData 就是字符串本身,委托里直接 text: modelData,比对象数组少一层字段访问。坑和上一个示例一样:增删必须用 concat / slice 造出新数组再赋回。行外观又回到最简单的 Rectangle------委托长什么样和模型没有绑定关系,模型只负责两件事:有几项、每项什么数据。
三种模型怎么选
| 维度 | 整数 | JavaScript 对象数组 | 字符串数组 |
|---|---|---|---|
model 写法 |
model: 4 |
model: [{ name: ..., color: ... }] |
model: ["a", "b"] |
| 委托里取数据 | index |
modelData.字段 |
modelData |
| 增删怎么写 | 改那个整数 | concat / slice 造新数组后赋值 |
同左 |
| 能不能原地改 | 不涉及 | 不能,必须重新赋值 | 不能,必须重新赋值 |
| 适合什么 | 固定条数的占位块 | 少量带字段的行数据 | 纯文本列表 |
如何选择?
数据有没有字段:只是"要几行",就写整数;每行有名字、颜色这类字段,就上对象数组。数据会不会变:会变就必须造新数组赋回去,图省事而又需要频繁增删的,直接看 ListModel 或 C++ 模型那一类结构化模型。
数组模型还有个副作用要留意。因为每次改数据都是整份重新赋值,Repeater 收到的相当于"模型换了",所有委托会推倒重建,委托里的临时状态(输入框里没提交的文字、勾选中的状态)会跟着丢。要频繁增删又不想丢状态,就得换成能按行增量增删的结构化模型。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_repeater