(更新日期:2020-2-7)
1.寫在前面
在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函數來做回調,用於設置行列寬高。
2.實現代碼
話不多說,直接上代碼:
(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 <QAbstractTableModel>
#include <QQmlParserStatus>
#include <QHash>
#include <QList>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
//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<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
{
//返回表頭數據,無效的返回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<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();
}