QML TableView 实例演示 + 可能遇到的一些问题(Qt_6_5_3)
一、可能遇到的一些问题
Q1:如何禁用拖动?
在TableView下加一句代码即可:
interactive: false
补充:这个属性并不专属于TableView,而是一个通用属性。很多Controls下的控件都可以使用,其主要作用就是控制交互的。
举个例子:像width、height、color这些都属于通用属性,并不是说只有一个控件需要指定它的宽高、颜色。这种通用特性在QML是非常常见的。
或许你想给某个控件实现某种功能,没准这个功能其实就是其他控件中的一个属性罢了。
Q2:设置了标题栏之后,程序直接卡死?
可能是Layout布局的问题,把Layout布局换成Item试试。
Q3:表头数据和内容数据重叠显示了?或者一片空白?
大概率还是布局的问题,检查一下吧!
补充:在 HorizontalHeaderView 中使用了 syncView 属性,就相当于是将 HorizontalHeaderView 和指定的 TableView 绑定了,它默认会有个布局,就是把水平标题栏放在表格内容的上方,但是左右的对齐方式可能并不是我们想要的,所以在做整体布局时需要注意这一点。
Q4:不想让用户编辑输入框?
将TextField设为只读就可以啦!
readOnly: true
还有其它问题?
上面的几个问题是我在做这个实例的时候遇到的,如果你有别的问题可以在评论区留言,我会逐一回复哦~
二、QML TableView 实例演示
下面截图中的红框部分就是最终的效果,虽然看似简陋,实际确实有点简陋。。。。
好消息是,这个只是我用来演示给大家看的,并不是我个人审美就这样。。。。所以,等大家学会了怎么去用 TableView 这个控件,就按照自己的想法和需求去设计它吧!
实现步骤1:TableModel
在QML中,像TableView、ListView这种控件,视图(View)和数据(model)都是分开的。所以我们需要依次去完善他们。
那么,先来实现一个model吧!
//表头数据放TableModelColumn里,内容数据放rows里,由TableModel统一管理
TableModel
{
id: tablemodel_2
TableModelColumn { display: "状态" } //后面表头会用到
TableModelColumn { display: "简称" }
TableModelColumn { display: "售价" }
TableModelColumn { display: "库存" }
rows: //表格里具体显示的内容
[
{
"状态": true,
"简称": "AK-74自动步枪",
"售价": 149,
"库存": 500
},
{
"状态": true,
"简称": "81式自动步枪",
"售价": 199,
"库存": 300
},
{
"状态": true,
"简称": "M16突击步枪",
"售价": 119,
"库存": 100
},
{
"状态": true,
"简称": "SCAR突击步枪",
"售价": 129,
"库存": 100
},
{
"状态": true,
"简称": "HKG36突击步枪",
"售价": 159,
"库存": 100
}
]
}
在上面的代码片段中,最外层是1个TableModel,这个TableModel和TableView是同级的,不是写在TableView下面的哈,文章最后我会把完整的代码也发出来。
我们这个TableModel定义1个id叫tablemodel_2,因为等会后面需要通过id来调用这个TableModel里面的数据。
接着,我们连续写了4行TableModelColumn,这个是后面要用到的水平标题栏的数据,每一个TableModelColumn就代表1列。
最后是1个 rows,这个rows后面紧跟的一个是列表[ ],列表里面放的就是除表头以外的数据了。形式就是花括号里一对对的键和值,而键就是上面TableModelColumn中的文本,也就是列名,值才是每个单元格中具体要显示出来的数据。
然后你可能会想到,哎呀~这么多行代码下去,实际就显示5行数据,如果列很多、数据成千上万,这代码得写多长啊!
这点不用担心啦!因为QML的专家早就考虑到了。如果数据量很少,而且是固定的,model直接写出来就行了。如果数据多,那最好用C++的模型类来提供。需要注意的是:C++模型必须是QAbstractItemModel的子类。
这个C++的模型类有点复杂,后面我会专门写一篇文章来介绍它。
实现步骤2:TableView
做完数据层(model),下面我们就来做具体的外观(View)吧!请看下面代码:
View层,大概分为2个部分,蓝色框框里是一些整体的属性设置,绿色框框里是针对每一列数据的显示细节的描述。
在delegate中,我们写了4个DelegateChoice。一共是5行数据,为什么我们只写了4个?因为如果你只是简单的显示model里的数据、不会有交互,那么就统一用一个DelegateChoice就行啦!
什么叫不会有交互?你看前面截图中第一列的数据是不是都是复选框?复选框是干嘛的?不就是给用户去勾选、去交互的嘛~
所以像这种后期会交互的数据列,就要单独的DelegateChoice去写。我展开第一个DelegateChoice中的代码给你看一下:
DelegateChoice {
column: 0 //指定哪一列可编辑
delegate: Rectangle
{
implicitWidth: 150
implicitHeight: 40
color: t_tableView_2.currentRow === row ? "#c8e5b3" : "#eeeeee"
CheckBox
{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
checked: model.display //CheckBox的默认状态就是模型里的值
onToggled: model.display = checked //编辑即更新数据到模型
Material.accent: "green"
}
}
}
看到了吗?里面竟然还有一个delegate?这个最里层的delegate就是设置具体的单元格的显示方式啦!
可能有了解ListView的小伙伴好奇了,我之前做ListView的时候只需要1个delegate就行了,怎么这个TableView还要套2层delegate,外面竟然还有个什么DelegateChoice?这个DelegateChoice又是什么?干啥用的?
这个DelegateChoice是按列去设置的,每一列都有不同的情况和需求,如果我们这个表格是涉及到交互、而不单单是给用户看看的,那就需要用DelegateChoice对具体的列去设置,因为可能第1列全部是复选框,第3列又全都是单行输入框,怎么都得去单独设置~
实现步骤3:HorizontalHeaderView
这个HorizontalHeaderView就是水平标题栏啦!
HorizontalHeaderView {
id: horizontalHeaderView
syncView: t_tableView_2
interactive: false //禁用拖动
delegate: Rectangle {
implicitWidth: 150; implicitHeight: 40; color: "transparent"
Text {
text: tablemodel_2.columns[index].display
font.bold: true
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
}
}
}
这个水平标题栏和TableView也是同级的,不要写到TableView里面去喽!
在QML中,标题栏和表格是分开设置的,所以设置了标题栏之后需要通过syncView属性去绑定具体的TableView。
然后这个水平标题栏也是默认有交互效果的,如果你不想让它动,就也用interactive属性禁用吧!
实现步骤4:降序、升序
排序功能就是对model中的数据进行冒泡排序,如果默认是降序,那么对降序取反就是升序啦!
冒泡排序是写在了MouseArea中的onClicked信号下,细心的小伙伴会发现,这个MouseArea覆盖的是Text,而不是Rectangle。
这是因为,如果直接覆盖Rectangle,那么HorizontalHeaderView自带的列宽调整功能就失效啦!
HorizontalHeaderView {
id: horizontalHeaderView
syncView: t_tableView_2
interactive: false //禁用拖动
property bool isDesc: true //默认表格内的数据是降序排列
delegate: Rectangle {
implicitWidth: 150; implicitHeight: 40; color: "transparent"
Text {
text: tablemodel_2.columns[index].display + " " + ( horizontalHeaderView.isDesc ? "∨" : "∧" ) //添加排序标识符号
font.bold: true
font.family: "Microsoft YaHei"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
MouseArea {
anchors.fill: parent //排序生效区域覆盖文字区域而不是整个矩形,否则表头的列宽调整功能将失效
onClicked: {
var headerKey = tablemodel_2.columns[index].display
for ( var i = tablemodel_2.rowCount -1; i > 0; i-- ) {
for ( var j = 0; j < i; j++ ) {
if ( horizontalHeaderView.isDesc ) {
if ( tablemodel_2.getRow(j)[headerKey] > tablemodel_2.getRow(j + 1)[headerKey]) {
tablemodel_2.moveRow(j, j + 1, 1)
}
}
else {
if ( tablemodel_2.getRow(j)[headerKey] < tablemodel_2.getRow(j + 1)[headerKey] ) {
tablemodel_2.moveRow(j, j + 1, 1)
}
}
}
}
horizontalHeaderView.isDesc = !horizontalHeaderView.isDesc
}
}
}
}
}
完整代码
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.qmlmodels //TableModel需要用到的库
Item {
//表头数据放TableModelColumn里,内容数据放rows里,由TableModel统一管理
TableModel
{
id: tablemodel_2
TableModelColumn { display: "状态" } //后面表头会用到
TableModelColumn { display: "简称" }
TableModelColumn { display: "售价" }
TableModelColumn { display: "库存" }
rows: //表格里具体显示的内容
[
{
"状态": true,
"简称": "AK-74自动步枪",
"售价": 149,
"库存": 500
},
{
"状态": true,
"简称": "81式自动步枪",
"售价": 199,
"库存": 300
},
{
"状态": true,
"简称": "M16突击步枪",
"售价": 119,
"库存": 100
},
{
"状态": true,
"简称": "SCAR突击步枪",
"售价": 129,
"库存": 100
},
{
"状态": true,
"简称": "HKG36突击步枪",
"售价": 159,
"库存": 100
}
]
}
TableView
{
id: t_tableView_2
width: contentWidth
height: contentHeight
anchors.top: horizontalHeaderView.bottom
interactive: false //禁止拖动
rowSpacing: 1 //行间距。列间距是columnSpacing
model: tablemodel_2
//行选择,选中事件的处理
selectionModel: ItemSelectionModel {}
//指定可编辑的列,把CheckBox控件放进表格中
delegate: DelegateChooser {
DelegateChoice {
column: 0 //指定哪一列可编辑
delegate: Rectangle
{
implicitWidth: 150
implicitHeight: 40
color: t_tableView_2.currentRow === row ? "#c8e5b3" : "#eeeeee"
CheckBox
{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
checked: model.display //CheckBox的默认状态就是模型里的值
onToggled: model.display = checked //编辑即更新数据到模型
Material.accent: "green"
}
}
}
DelegateChoice {
column: 2
delegate: Rectangle
{
implicitWidth: 150
implicitHeight: 40
color: t_tableView_2.currentRow === row ? "#c8e5b3" : "#eeeeee"
BasicTextField
{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
width: 80; height: 30
text: model.display
onAccepted: model.display = text //回车更新数据
onEditingFinished: model.display = text //焦点改变更新数据
}
}
}
DelegateChoice {
column: 3
delegate: Rectangle
{
implicitWidth: 150; implicitHeight: 40
color: t_tableView_2.currentRow === row ? "#c8e5b3" : "#eeeeee"
BasicTextField
{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
width: 80; height: 30
text: model.display
onAccepted: model.display = text //回车更新数据
onEditingFinished: model.display = text //焦点改变更新数据
}
}
}
DelegateChoice //默认显示方式,不指定具体列
{
delegate: Rectangle
{
implicitWidth: 150; implicitHeight: 40
//选中行变色(生效前提是:为selectionModel分配一个ItemSelectionModel)
color: t_tableView_2.currentRow === row ? "#c8e5b3" : "#eeeeee"
Text
{
text: display
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
}
}
}
}
}
HorizontalHeaderView {
id: horizontalHeaderView
syncView: t_tableView_2
interactive: false //禁用拖动
delegate: Rectangle {
implicitWidth: 150; implicitHeight: 40; color: "transparent"
Text {
text: tablemodel_2.columns[index].display
font.bold: true
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
}
}
}
}