(更新日期:-2-7)
1.写在前面
在Qt5.12中,QtQuick 2 添加了 TableView 组件,功能和 QtQuickControl 1 中的 TableView 类似,但是接口大不一样(QtQuick Control 1已经处于弃用状态,不建议使用)。
Qt Creator中有两个 QtQuick 2 TableView 的示例,但是都不是数据类型的,参考起来不大方便,我也是别人的 Demo 以及 Qt 文档学习了下(参考链接/yuriyoung/qml-examples)。文本主要是用代码演示TableView的基本操作,即数据的展示和编辑。下面是效果图(样式随意写的,不同的颜色便于调试时区分):
代码主要由两部分,一是自定义 TableView(包括 item 的 delegate ,滚动条、列宽拖动等),二是使用C++定义一个TableModel(数据通过 json 格式转换)。这里面比较有意思的就是 TableView 用 xxxWidthProvider 使用JS函数来做回调,用于设置行列宽高。
2.实现代码
话不多说,直接上代码:
(git链接:/gongjianbo/QmlModelView.git)
(先是QML部分)
//TableWidget.qmlimport QtQuick 2.12import QtQuick.Controls 2.12import EasyModel 1.0//自定义QtQuick 2中的TableViewItem {id: controlimplicitHeight: 300implicitWidth: 500//行表头-竖向的property int verHeaderHeight: 30property 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_modelhorHeader: ["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_viewanchors{fill: parentleftMargin: control.verHeaderWidthtopMargin: control.horHeaderHeight}clip: trueboundsBehavior: Flickable.StopAtBoundscolumnSpacing: 1rowSpacing: 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_verticalanchors.right: parent.rightanchors.rightMargin: 2//active: table_view.ScrollBar.vertical.active//policy: ScrollBar.AsNeededcontentItem: Rectangle{visible: (scroll_vertical.size<1.0)implicitWidth: control.scrollBarWidthcolor: control.scrollBarColor}}ScrollBar.horizontal: ScrollBar {id: scroll_horizontalanchors.bottom: parent.bottomanchors.bottomMargin: 2//active: table_view.ScrollBar.vertical.active//policy: ScrollBar.AsNeededcontentItem: Rectangle{visible: (scroll_horizontal.size<1.0)implicitHeight: control.scrollBarWidthcolor: control.scrollBarColor}}//model是在C++中定义的,和QtC++是类似的model: table_modeldelegate: Rectangle{color: (model.row%2)?"orange":Qt.darker("orange")TextInput{anchors.fill: parentverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCenter//elide: Text.ElideRightselectByMouse: trueselectedTextColor: "black"selectionColor: "white"//获取单元格对应的值text: model.valueonEditingFinished: {model.edit=text;console.log("edit",model.value)}}}}//横项表头Item{id: header_horizontalanchors{left: parent.leftright: parent.rightleftMargin: control.verHeaderWidth}height: control.horHeaderHeightz: 2//暂存鼠标拖动的位置property int posXTemp: 0MouseArea{anchors.fill: parentonPressed: 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_rowanchors.fill: parentleftPadding: -table_view.contentXclip: truespacing: 0Repeater {model: table_view.columns > 0 ? table_view.columns : 0Rectangle {id: header_horizontal_itemwidth: table_view.columnWidthProvider(index)+table_view.columnSpacingheight: control.horHeaderHeightcolor: "purple"Text {anchors.centerIn: parenttext: table_model.headerData(index, Qt.Horizontal)}Rectangle{width: 1height: parent.heightanchors.right: parent.rightcolor: "black"opacity: 0.5}MouseArea{width: 3height: parent.heightanchors.right: parent.rightcursorShape: Qt.SplitHCursoronPressed: 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_vericalanchors{top: parent.topbottom: parent.bottomtopMargin: control.horHeaderHeight}topPadding: -table_view.contentYz: 2clip: truespacing: 1Repeater {model: table_view.rows > 0 ? table_view.rows : 0Rectangle {width: control.verHeaderWidthheight: table_view.rowHeightProvider(index)color: "green"Text {anchors.centerIn: parenttext: table_model.headerData(index, Qt.Vertical)}}}}}
//main.qmlimport QtQuick 2.12import QtQuick.Window 2.12Window {visible: truewidth: 640height: 480title: qsTr("QQ群:647637553")Rectangle{anchors.fill: parentanchors.margins: 20color: "gray"TableWidget{anchors.fill: parent}}}
(接下来是C++定义的TableModel)
//EasyTableModel.h#ifndef EASYTABLEMODEL_H#define EASYTABLEMODEL_H#include <QAbstractTableModel>#include <QQmlParserStatus>#include <QHash>#include <QList>#include <QJsonArray>#include <QJsonObject>#include <QJsonValue>//tableview的简易modelclass EasyTableModel : public QAbstractTableModel, public QQmlParserStatus{Q_OBJECTQ_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;// 自定义roleQHash<int,QByteArray> 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<QVector<QVariant>> _modelData;// 横项表头QList<QString> _horHeaderList;};#endif // EASYTABLEMODEL_H
//EasyTableModel.cpp#include "EasyTableModel.h"#include <QDebug>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<int, QByteArray> EasyTableModel::roleNames() const{//value表示取值,edit表示编辑return QHash<int,QByteArray>{{ Qt::DisplayRole,"value" },{ Qt::EditRole,"edit" }};}QVariant EasyTableModel::headerData(int section, Qt::Orientation orientation, int role) const{//返回表头数据,无效的返回Noneif(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<int>() << 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<QVector<QVariant>> newData;QJsonArray::const_iterator iter;for(iter=data.begin();iter!=data.end();++iter){QVector<QVariant> 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 <QGuiApplication>#include <QQmlApplicationEngine>#include "EasyTableModel.h"int main(int argc, char *argv[]){QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);QQmlApplicationEngine engine;qmlRegisterType<EasyTableModel>("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();}