(更新日期:2020-2-7)
在Qt5.12中,QtQuick 2 添加了 TableView 组件,功能和 QtQuick Control 1 中的 TableView 类似,但是接口大不一样(QtQuick Control 1已经处于弃用状态,不建议使用)。
Qt Creator中有两个 QtQuick 2 TableView 的示例,但是都不是数据类型的,参考起来不大方便,我也是别人的 Demo 以及 Qt 文档学习了下(参考链接 https://github.com/yuriyoung/qml-examples)。文本主要是用代码演示TableView的基本操作,即数据的展示和编辑。下面是效果图(样式随意写的,不同的颜色便于调试时区分):
代码主要由两部分,一是自定义 TableView(包括 item 的 delegate ,滚动条、列宽拖动等),二是使用C++定义一个TableModel(数据通过 json 格式转换)。这里面比较有意思的就是 TableView 用 xxxWidthProvider 使用JS函数来做回调,用于设置行列宽高。
话不多说,直接上代码:
(git链接:https://github.com/gongjianbo/QmlModelView.git)
(先是QML部分)
//TableWidget.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import EasyModel 1.0
//自定义QtQuick 2中的TableView
Item {
id: control
implicitHeight: 300
implicitWidth: 500
//行表头-竖向的
property int verHeaderHeight: 30
property int verHeaderWidth: 30
//列表头-横向的
property int horHeaderHeight: 30
//property int horHeaderWidth: 30
//滚动条
property color scrollBarColor: "cyan"
property int scrollBarWidth: 6
//列宽
property variant columnWidthArr: [100,100,100,200]
EasyTableModel{
id: table_model
horHeader: ["Id","Name","Age","Note"]
initData: [
{"id":1,"name":"gonge","age":20,"note":"test model view"},
{"id":2,"name":"gonge","age":21,"note":"test model view"},
{"id":3,"name":"gonge","age":22,"note":"test model view"},
{"id":4,"name":"gonge","age":23,"note":"test model view"},
{"id":5,"name":"gonge","age":24,"note":"test model view"},
{"id":6,"name":"gonge","age":25,"note":"test model view"},
{"id":7,"name":"gonge","age":26,"note":"test model view"},
{"id":8,"name":"gonge","age":27,"note":"test model view"}
]
}
//表格内容(不包含表头)
TableView{
id: table_view
anchors{
fill: parent
leftMargin: control.verHeaderWidth
topMargin: control.horHeaderHeight
}
clip: true
boundsBehavior: Flickable.StopAtBounds
columnSpacing: 1
rowSpacing: 1
//视图的高度
//contentHeight: rowHeightProvider(0) * rows + rowHeightProvider(rows-1)
//视图的宽度
//contentWidth:
//content内容区域边距,但是不影响滚动条的位置
//leftMargin:
//topMargin:
//此属性可以包含一个函数,该函数返回模型中每行的行高
rowHeightProvider: function (row) {
return control.verHeaderHeight;
}
//此属性可以保存一个函数,该函数返回模型中每个列的列宽
columnWidthProvider: function (column) {
return control.columnWidthArr[column];
//return Math.max(1, (table_view.width - leftMargin) / table_view.columns)
}
ScrollBar.vertical: ScrollBar {
id: scroll_vertical
anchors.right: parent.right
anchors.rightMargin: 2
//active: table_view.ScrollBar.vertical.active
//policy: ScrollBar.AsNeeded
contentItem: Rectangle{
visible: (scroll_vertical.size<1.0)
implicitWidth: control.scrollBarWidth
color: control.scrollBarColor
}
}
ScrollBar.horizontal: ScrollBar {
id: scroll_horizontal
anchors.bottom: parent.bottom
anchors.bottomMargin: 2
//active: table_view.ScrollBar.vertical.active
//policy: ScrollBar.AsNeeded
contentItem: Rectangle{
visible: (scroll_horizontal.size<1.0)
implicitHeight: control.scrollBarWidth
color: control.scrollBarColor
}
}
//model是在C++中定义的,和QtC++是类似的
model: table_model
delegate: Rectangle{
color: (model.row%2)?"orange":Qt.darker("orange")
TextInput{
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//elide: Text.ElideRight
selectByMouse: true
selectedTextColor: "black"
selectionColor: "white"
//获取单元格对应的值
text: model.value
onEditingFinished: {
model.edit=text;
console.log("edit",model.value)
}
}
}
}
//横项表头
Item{
id: header_horizontal
anchors{
left: parent.left
right: parent.right
leftMargin: control.verHeaderWidth
}
height: control.horHeaderHeight
z: 2
//暂存鼠标拖动的位置
property int posXTemp: 0
MouseArea{
anchors.fill: parent
onPressed: header_horizontal.posXTemp=mouseX;
onPositionChanged: {
if(table_view.contentX+(header_horizontal.posXTemp-mouseX)>0){
table_view.contentX+=(header_horizontal.posXTemp-mouseX);
}else{
table_view.contentX=0;
}
header_horizontal.posXTemp=mouseX;
}
}
Row {
id: header_horizontal_row
anchors.fill: parent
leftPadding: -table_view.contentX
clip: true
spacing: 0
Repeater {
model: table_view.columns > 0 ? table_view.columns : 0
Rectangle {
id: header_horizontal_item
width: table_view.columnWidthProvider(index)+table_view.columnSpacing
height: control.horHeaderHeight
color: "purple"
Text {
anchors.centerIn: parent
text: table_model.headerData(index, Qt.Horizontal)
}
Rectangle{
width: 1
height: parent.height
anchors.right: parent.right
color: "black"
opacity: 0.5
}
MouseArea{
width: 3
height: parent.height
anchors.right: parent.right
cursorShape: Qt.SplitHCursor
onPressed: header_horizontal.posXTemp=mouseX;
onPositionChanged: {
if((header_horizontal_item.width-(header_horizontal.posXTemp-mouseX))>10){
header_horizontal_item.width-=(header_horizontal.posXTemp-mouseX);
}else{
header_horizontal_item.width=10;
}
header_horizontal.posXTemp=mouseX;
control.columnWidthArr[index]=(header_horizontal_item.width-table_view.columnSpacing);
//刷新布局,这样宽度才会改变
table_view.forceLayout();
}
}
}
}
}
}
//竖向表头
Column {
id: header_verical
anchors{
top: parent.top
bottom: parent.bottom
topMargin: control.horHeaderHeight
}
topPadding: -table_view.contentY
z: 2
clip: true
spacing: 1
Repeater {
model: table_view.rows > 0 ? table_view.rows : 0
Rectangle {
width: control.verHeaderWidth
height: table_view.rowHeightProvider(index)
color: "green"
Text {
anchors.centerIn: parent
text: table_model.headerData(index, Qt.Vertical)
}
}
}
}
}
//main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("QQ群:647637553")
Rectangle{
anchors.fill: parent
anchors.margins: 20
color: "gray"
TableWidget{
anchors.fill: parent
}
}
}
(接下来是C++定义的TableModel)
//EasyTableModel.h
#ifndef EASYTABLEMODEL_H
#define EASYTABLEMODEL_H
#include
#include
#include
#include
#include
#include
#include
//tableview的简易model
class EasyTableModel : public QAbstractTableModel, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QStringList horHeader READ getHorHeader WRITE setHorHeader NOTIFY horHeaderChanged)
Q_PROPERTY(QJsonArray initData READ getInitData WRITE setInitData NOTIFY initDataChanged)
public:
explicit EasyTableModel(QObject *parent = nullptr);
QStringList getHorHeader() const;
void setHorHeader(const QStringList &header);
QJsonArray getInitData() const;
void setInitData(const QJsonArray &jsonArr);
// QQmlParserStatus:构造前
void classBegin() override;
// QQmlParserStatus:构造后
void componentComplete() override;
// 自定义role
QHash roleNames() const override;
// 表头
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override;
// 数据,这三个必须实现
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// 编辑
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
private:
void loadData(const QJsonArray &data);
signals:
void horHeaderChanged();
void initDataChanged();
private:
// 组件是否初始化完成
bool _completed=false;
// 加载的数据
QJsonArray _initData;
// 数据,我一般纯展示,用vector就行了
QVector> _modelData;
// 横项表头
QList _horHeaderList;
};
#endif // EASYTABLEMODEL_H
//EasyTableModel.cpp
#include "EasyTableModel.h"
#include
EasyTableModel::EasyTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
QStringList EasyTableModel::getHorHeader() const
{
return _horHeaderList;
}
void EasyTableModel::setHorHeader(const QStringList &header)
{
_horHeaderList=header;
emit horHeaderChanged();
}
QJsonArray EasyTableModel::getInitData() const
{
return _initData;
}
void EasyTableModel::setInitData(const QJsonArray &jsonArr)
{
_initData=jsonArr;
if(_completed){
loadData(_initData);
}
emit initDataChanged();
}
void EasyTableModel::classBegin()
{
qDebug()<<"EasyTableModel::classBegin()";
}
void EasyTableModel::componentComplete()
{
qDebug()<<"EasyTableModel::componentComplete()";
_completed=true;
if(!_initData.isEmpty()){
loadData(_initData);
}
}
QHash EasyTableModel::roleNames() const
{
//value表示取值,edit表示编辑
return QHash{
{ Qt::DisplayRole,"value" },
{ Qt::EditRole,"edit" }
};
}
QVariant EasyTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
//返回表头数据,无效的返回None
if(role==Qt::DisplayRole){
if(orientation==Qt::Horizontal){
return _horHeaderList.value(section,QString::number(section));
}else if(orientation==Qt::Vertical){
return QString::number(section);
}
}
return QVariant();
}
bool EasyTableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (value != headerData(section, orientation, role)) {
if(orientation==Qt::Horizontal&&role==Qt::EditRole){
_horHeaderList[section]=value.toString();
emit headerDataChanged(orientation, section, section);
return true;
}
}
return false;
}
int EasyTableModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return _modelData.count();
}
int EasyTableModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return _horHeaderList.count();
}
QVariant EasyTableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return _modelData.at(index.row()).at(index.column());
default:
break;
}
return QVariant();
}
bool EasyTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (value.isValid()&&index.isValid()&&(data(index, role) != value)) {
if(Qt::EditRole==role){
_modelData[index.row()][index.column()]=value;
emit dataChanged(index, index, QVector() << role);
return true;
}
}
return false;
}
Qt::ItemFlags EasyTableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable;
}
void EasyTableModel::loadData(const QJsonArray &data)
{
//如果要区分类型的话,可以用role,
//这样ui中就能使用model.role来获取对应index的参数
QVector> newData;
QJsonArray::const_iterator iter;
for(iter=data.begin();iter!=data.end();++iter){
QVector newRow;
const QJsonObject itemRow=(*iter).toObject();
newRow.append(itemRow.value("id"));
newRow.append(itemRow.value("name"));
newRow.append(itemRow.value("age"));
newRow.append(itemRow.value("note"));
newData.append(newRow);
}
emit beginResetModel();
_modelData=newData;
emit endResetModel();
}
//main.cpp
#include
#include
#include "EasyTableModel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType("EasyModel",1,0,"EasyTableModel");
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}