ListView基础、ListModel、Delegate设计、高亮与选择
本章将深入探讨QT Quick中用于数据展示的核心组件——模型与视图框架。通过ListView及其相关组件,学习如何高效、美观地呈现列表数据。
QT Quick的模型-视图-委托(MVD)架构,将数据的存储、显示和可视化呈现方式彻底分离,实现了高度解耦与灵活性,让数据管理、界面展示和交互逻辑各自独立,便于维护和扩展。
ListView是QT Quick中用于显示垂直或水平列表数据的核心组件,它会将模型中的数据按顺序排列,同时处理滚动、虚拟化等性能优化,即使是大数据集也能流畅运行。
ListView的核心属性决定了它的功能和表现:
下面是一个简单的ListView示例,显示20个交替背景的列表项:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
width: 300
height: 400
visible: true
ListView {
anchors.fill: parent
model: 20 // 简单模型:生成20个空项
delegate: Rectangle {
width: ListView.view.width
height: 50
color: index % 2 ? "#F5F5F5" : "#FFFFFF"
border.color: "#E0E0E0"
Text {
anchors.centerIn: parent
text: "列表项 " + (index + 1)
font.pixelSize: 16
color: "#333333"
}
}
}
}
ListModel是QT Quick中最简单的动态数据模型,完全在QML中定义和修改,无需编写C++代码,适合中小型数据集。每个数据项由ListElement组成,每个元素可以包含多个命名“角色”(即数据字段)。
我们可以直接在QML中定义ListModel,并在ListView中引用它:
import QtQuick 2.15
// 定义水果模型
ListModel {
id: fruitModel
ListElement {
name: "苹果"
color: "red"
price: 5.5
icon: "🍎"
}
ListElement {
name: "香蕉"
color: "yellow"
price: 3.2
icon: "🍌"
}
ListElement {
name: "葡萄"
color: "purple"
price: 8.0
icon: "🍇"
}
}
// 在ListView中使用模型
ListView {
width: 200
height: 300
model: fruitModel
delegate: Text {
text: icon + " " + name + " - ¥" + price
color: model.color
}
}
ListModel支持动态增删改查数据项,常用方法如下:
// 添加新水果项
fruitModel.append({"name": "橙子", "color": "orange", "price": 4.5, "icon": "🍊"})
// 在索引1的位置插入新项
fruitModel.insert(1, {"name": "草莓", "color": "pink", "price": 12.0, "icon": "🍓"})
// 修改索引1项的价格
fruitModel.setProperty(1, "price", 10.5)
// 移除索引0的项
fruitModel.remove(0)
// 清空所有数据
fruitModel.clear()
// 获取模型中数据项的数量
var count = fruitModel.count
委托是列表项的“模板”,定义了每个数据项的外观和交互行为。它在ListView的上下文执行,可以直接访问模型数据、当前项索引等信息。
在委托组件中,你可以直接使用这些属性来获取上下文信息:
model.name可以获取当前项的名称字段。下面是一个模拟联系人列表的高级委托,包含鼠标交互效果、头像和状态显示:
Component {
id: contactDelegate
Rectangle {
id: delegateRoot
width: ListView.view.width
height: 70
color: ListView.isCurrentItem ? "#E3F2FD" : (index % 2 ? "#FAFAFA" : "white")
border.color: "#E0E0E0"
radius: 5
// 鼠标交互:点击选中、 hover时改变边框颜色
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
delegateRoot.ListView.view.currentIndex = index
console.log("选中:", model.name)
}
onEntered: parent.border.color = "#2196F3"
onExited: parent.border.color = "#E0E0E0"
}
Row {
anchors.fill: parent
anchors.margins: 10
spacing: 15
// 头像:用名字首字母和背景色生成
Rectangle {
width: 50
height: 50
radius: 25
color: model.avatarColor
anchors.verticalCenter: parent.verticalCenter
Text {
anchors.centerIn: parent
text: model.name.charAt(0)
font.bold: true
font.pixelSize: 20
color: "white"
}
}
// 联系人信息区域
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Text {
text: model.name
font.bold: true
font.pixelSize: 16
color: "#333333"
}
Text {
text: model.phone
font.pixelSize: 14
color: "#666666"
}
}
// 在线状态显示
Item {
width: 60
height: parent.height
Text {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: model.online ? "在线" : "离线"
color: model.online ? "#4CAF50" : "#9E9E9E"
font.pixelSize: 12
}
}
}
}
}
ListView提供了独立的高亮组件来标识当前选中项,它与委托组件分离,便于单独控制高亮效果,还能设置移动动画让高亮跟随选中项平滑切换。
下面是一个简单的高亮示例,为选中项添加浅蓝色背景和边框,并设置移动动画:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Rectangle {
id: root
width: StackView.view ? StackView.view.width : 700
height: StackView.view ? StackView.view.height : 800
color: "#F0F2F5"
// 顶部返回栏
Rectangle {
id: headerBar
anchors { left: parent.left; right: parent.right; top: parent.top }
height: 56
color: "#FFFFFF"
z: 10
Rectangle {
anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
height: 1; color: "#E0E0E0"
}
RowLayout {
anchors { fill: parent; leftMargin: 16; rightMargin: 16 }
Button {
text: "← 返回"
flat: true
font.pixelSize: 14
onClicked: { StackView.view.pop() }
}
Text {
text: "ListView 模型与视图"
font.pixelSize: 16; font.bold: true; color: "#1A1A2E"
}
Item { Layout.fillWidth: true }
}
}
// 数据模型
ListModel {
id: myModel
ListElement { name: "项目 Alpha"; progress: 75; status: "进行中" }
ListElement { name: "项目 Beta"; progress: 30; status: "起步" }
ListElement { name: "项目 Gamma"; progress: 100; status: "已完成" }
ListElement { name: "项目 Delta"; progress: 50; status: "进行中" }
ListElement { name: "项目 Epsilon"; progress: 90; status: "收尾" }
}
// 代理组件
Component {
id: myDelegate
Rectangle {
width: ListView.view.width
height: 60
color: index % 2 === 0 ? "#FFFFFF" : "#F8F9FA"
RowLayout {
anchors { fill: parent; leftMargin: 16; rightMargin: 16 }
spacing: 12
Rectangle {
width: 8; height: 8; radius: 4
color: status === "已完成" ? "#4CAF50" : (status === "进行中" ? "#2196F3" : "#FF9800")
}
Text {
text: name
font.pixelSize: 14
font.bold: true
color: "#1A1A2E"
Layout.preferredWidth: 120
}
Rectangle {
Layout.fillWidth: true
height: 8
radius: 4
color: "#E0E0E0"
Rectangle {
anchors { left: parent.left; top: parent.top; bottom: parent.bottom }
width: parent.width * progress / 100
radius: 4
color: status === "已完成" ? "#4CAF50" : (status === "进行中" ? "#2196F3" : "#FF9800")
}
}
Text {
text: progress + "%"
font.pixelSize: 13
color: "#666666"
Layout.preferredWidth: 36
}
Rectangle {
width: 56; height: 24; radius: 12
color: status === "已完成" ? "#E8F5E9" : (status === "进行中" ? "#E3F2FD" : "#FFF3E0")
Text {
anchors.centerIn: parent
text: status
font.pixelSize: 11
color: status === "已完成" ? "#4CAF50" : (status === "进行中" ? "#2196F3" : "#FF9800")
}
}
}
// 分割线
Rectangle {
anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
height: 1
color: "#EEEEEE"
}
}
}
ListView {
id: listView
anchors {
top: headerBar.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
leftMargin: 20
rightMargin: 20
topMargin: 20
}
clip: true
model: myModel
delegate: myDelegate
spacing: 0
// 定义高亮组件
highlight: Rectangle {
color: "#E3F2FD"
border.color: "#2196F3"
border.width: 2
radius: 5
width: listView.width - 10
x: 5
}
// 高亮跟随选中项移动,设置动画时长
highlightFollowsCurrentItem: true
highlightMoveDuration: 200 // 移动动画时长(毫秒)
highlightResizeDuration: 100 // 大小变化动画时长
}
}
你还可以设计更复杂的高亮组件,比如添加左侧指示条和选中标记:
Component {
id: customHighlight
Item {
width: ListView.view.width
height: 70
// 半透明背景高亮
Rectangle {
anchors.fill: parent
anchors.margins: 2
color: "#FFF3E0"
border.color: "#FF9800"
border.width: 2
radius: 8
opacity: 0.8
}
// 左侧橙色指示条
Rectangle {
width: 6
height: parent.height
color: "#FF9800"
radius: 3
}
// 右侧选中标记
Text {
anchors.right: parent.right
anchors.rightMargin: 15
anchors.verticalCenter: parent.verticalCenter
text: "✓"
color: "#FF9800"
font.bold: true
font.pixelSize: 20
}
}
}
// 在ListView中使用自定义高亮
ListView {
highlight: customHighlight
highlightRangeMode: ListView.ApplyRange // 确保高亮始终可见
preferredHighlightBegin: 100
preferredHighlightEnd: 300
}
默认ListView是单选模式,我们可以通过自定义逻辑实现多选功能:
ListView {
id: multiSelectList
width: 300
height: 400
// 存储选中项的索引数组
property var selectedIndexes: []
model: ListModel {
// 模型数据定义
}
delegate: Rectangle {
width: parent.width
height: 50
// 根据是否选中设置背景色
color: {
if (multiSelectList.selectedIndexes.indexOf(index) !== -1) {
return "#C8E6C9" // 选中状态颜色
}
return index % 2 ? "#F5F5F5" : "white"
}
Text {
anchors.centerIn: parent
text: model.text
}
MouseArea {
anchors.fill: parent
onClicked: {
var idx = multiSelectList.selectedIndexes.indexOf(index)
if (idx === -1) {
// 未选中则添加到选中列表
multiSelectList.selectedIndexes.push(index)
} else {
// 已选中则移除
multiSelectList.selectedIndexes.splice(idx, 1)
}
// 强制更新委托显示
multiSelectList.modelChanged()
}
}
}
}
结合本章所学,我们来实现一个完整的学生名单管理界面,包含数据增删、选中高亮、成绩分级显示等功能:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
width: 500
height: 600
visible: true
title: "学生名单管理系统"
// 数据模型
ListModel {
id: studentModel
ListElement { name: "张三"; id: "2023001"; score: 85; major: "计算机科学" }
ListElement { name: "李四"; id: "2023002"; score: 92; major: "软件工程" }
ListElement { name: "王五"; id: "2023003"; score: 78; major: "人工智能" }
ListElement { name: "赵六"; id: "2023004"; score: 88; major: "数据科学" }
ListElement { name: "钱七"; id: "2023005"; score: 95; major: "网络安全" }
}
// 主界面
Column {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// 标题
Text {
text: "📚 学生名单"
font.bold: true
font.pixelSize: 28
color: "#4CAF50"
}
// 操作栏
Row {
spacing: 10
Button {
text: "添加学生"
onClicked: {
studentModel.append({
"name": "新学生",
"id": "2023" + (100 + studentModel.count),
"score": Math.floor(Math.random() * 40) + 60,
"major": "未指定"
})
}
background: Rectangle {
color: "#4CAF50"
radius: 5
}
contentItem: Text {
text: parent.text
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Button {
text: "删除选中"
enabled: listView.currentIndex !== -1
onClicked: studentModel.remove(listView.currentIndex)
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "共 " + studentModel.count + " 名学生"
color: "#666666"
}
}
// 列表视图
ListView {
id: listView
width: parent.width
height: parent.height - 100
model: studentModel
spacing: 5
clip: true
// 高亮组件
highlight: Rectangle {
color: "#E8F5E9"
border.color: "#4CAF50"
border.width: 2
radius: 8
width: listView.width
}
highlightMoveDuration: 150
// 委托组件
delegate: Rectangle {
id: delegateItem
width: listView.width
height: 80
radius: 8
color: ListView.isCurrentItem ? "transparent" : (index % 2 ? "#F9F9F9" : "white")
border.color: "#E0E0E0"
border.width: 1
MouseArea {
anchors.fill: parent
onClicked: listView.currentIndex = index
onDoubleClicked: console.log("双击:", model.name)
}
Row {
anchors.fill: parent
anchors.margins: 15
spacing: 20
// 学号
Column {
anchors.verticalCenter: parent.verticalCenter
Text {
text: "学号"
font.pixelSize: 12
color: "#999999"
}
Text {
text: model.id
font.bold: true
font.pixelSize: 16
color: "#2196F3"
}
}
// 姓名
Column {
anchors.verticalCenter: parent.verticalCenter
width: 100
Text {
text: "姓名"
font.pixelSize: 12
color: "#999999"
}
Text {
text: model.name
font.bold: true
font.pixelSize: 18
color: "#333333"
}
}
// 专业
Column {
anchors.verticalCenter: parent.verticalCenter
width: 120
Text {
text: "专业"
font.pixelSize: 12
color: "#999999"
}
Text {
text: model.major
font.pixelSize: 14
color: "#666666"
}
}
// 成绩
Column {
anchors.verticalCenter: parent.verticalCenter
Text {
text: "成绩"
font.pixelSize: 12
color: "#999999"
}
Rectangle {
width: 60
height: 30
radius: 15
color: {
if (model.score >= 90) return "#4CAF50";
if (model.score >= 80) return "#8BC34A";
if (model.score >= 70) return "#FFC107";
return "#F44336";
}
Text {
anchors.centerIn: parent
text: model.score
font.bold: true
color: "white"
}
}
}
}
// 右侧操作按钮
Button {
anchors.right: parent.right
anchors.rightMargin: 15
anchors.verticalCenter: parent.verticalCenter
text: "编辑"
flat: true
onClicked: {
listView.currentIndex = index
editDialog.open()
}
}
}
// 空列表提示
Label {
anchors.centerIn: parent
text: "暂无学生数据\n点击上方按钮添加"
visible: studentModel.count === 0
horizontalAlignment: Text.AlignHCenter
color: "#999999"
}
}
}
}