QML TableView 自定义与使用(QtQuick.Controls 2、标题绘制)

背景

QtQuick.Controls 1和QtQuick.Controls2的TableView在用法上有很大差异,而且Qt帮助手册对于QtQuick.Controls2的TableView表格行、列表头实现介绍不多。

因此,做了个自定义TableView控件(MyTableView),涉及 model/view、表头绘制、表头冻结拖拽、表头跟随表格内容移动、表格内容显示、拖拽表头调节表格行列宽度高度、滚动条显示等功能

一方面是提高对QML的熟练度,另一方面是以后其他项目也用得上,废话不多说,先上效果图。

效果展示

基本设计思路

  1. 自定义model C++类,继承QAbstractTableModel,实现几个必要的虚函数,如:rowCount()、columnCount()、roleNames()、data()等等(qt的model/view)
  2. 将自定义model C++类注册到QML中,或者创建一个实例对象添加为QML上下文数属性(C++与QML混合编程)
  3. MyTableView.qml中,使用TableView绘制表格内容,横向和竖向表头分别使用Rectangle来实现,在上面添加Text、MouseArea以显示文本和响应鼠标事件,其x、y与TableView的contentX、contentY关联在一起,就可以跟随表格的拖拽来回移动了
    (当然,如果表头使用Flickable或者ListView实现会更方便一点)
    控件树结构如下:

自定义model类

这个模型是用来显示数据库数据的,所以行列的内容都是动态的,数据类型是自定义的DbData

关键点:将model应用于QML中,与在QWidget上使用的不同点是需要实现虚函数roleNames(),将role序号与role名称字符串关联起来,然后在QML中直接使用role名称,就能实现关联。

/*此处列举一下相关的代码*/

//定义
const QByteArray s_roleName_display("role_display");
const int s_roleIndex_scoreHistory = Qt::DisplayRole;
QHash<int, QByteArray> ScoreHistoryModel::roleNames() const
{
	return m_roleNames;
}

//初始化
m_roleNames.clear();
m_roleNames.insert(s_roleIndex_scoreHistory, s_roleName_display);


//QML中的使用
//表格内单元格委托
    Component {
        id: tableCellDelegate
        Rectangle {
            id: tableCell
            implicitWidth: root.columnHeaderCellWidthMin
            implicitHeight: root.rowHeaderCellHeightMin
            color: "transparent"
            border.color: root.contentBorderColor
            border.width: 1
            clip: true

            Text {
                id: tableCellText
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
                text: role_display //这里就是role的名称
                color: root.contentTextColor
                font.weight: Font.Bold
                font.pointSize: root.textPointSize
。。。。
。。。。

ScoreHistoryModel.h

#pragma once

#include <QObject>
#include <QHash>
#include <QAbstractTableModel>
#include "Database_def.h"

class ScoreHistoryModel  : public QAbstractTableModel
{
	Q_OBJECT

public:
	ScoreHistoryModel(QObject* parent = nullptr);
	~ScoreHistoryModel();

	DbData getScoreHistoryData() const;
	void setScoreHistoryData(const DbData& data);
	Q_INVOKABLE QString GetHorizontalHeaderName(int section) const;
	Q_INVOKABLE QString GetVerticalHeaderName(int section) const;

protected:
	virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
	virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
	virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
	virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
	virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
	virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
	virtual QHash<int, QByteArray> roleNames() const;

signals:
	void allDataUpdated(); //数据更新信号,用于qml表格内容布局刷新

private:
	void ClearScoreHistoryData();

private:
	QHash<int, QByteArray> m_roleNames;
	mutable DbData m_scoreHistoryData;

};

ScoreHistoryModel.cpp

#include "ScoreHistoryModel.h"


const QByteArray s_roleName_display("role_display");
const int s_roleIndex_scoreHistory = Qt::DisplayRole;

ScoreHistoryModel::ScoreHistoryModel(QObject *parent)
	: QAbstractTableModel(parent)
{
	m_roleNames.clear();
	m_roleNames.insert(s_roleIndex_scoreHistory, s_roleName_display);
}

ScoreHistoryModel::~ScoreHistoryModel()
{}

DbData ScoreHistoryModel::getScoreHistoryData() const
{
	return m_scoreHistoryData;
}

void ScoreHistoryModel::setScoreHistoryData(const DbData& data)
{
	int rowCount = data.rows.count() - 1;
	int columnCount = data.fieldGroup.fields.size() - 1;
	if (rowCount >= 0 && columnCount >= 0)
	{
		ClearScoreHistoryData();
		beginInsertRows(QModelIndex(), 0, rowCount);
		beginInsertColumns(QModelIndex(), 0, columnCount);
		m_scoreHistoryData = data;
		endInsertColumns();
		endInsertRows();
		emit allDataUpdated();
	}
}

void ScoreHistoryModel::ClearScoreHistoryData()
{
	int rowCount = m_scoreHistoryData.rows.count() - 1;
	int columnCount = m_scoreHistoryData.fieldGroup.fields.size() - 1;
	if (rowCount >= 0)
	{
		beginRemoveRows(QModelIndex(), 0, rowCount);
		m_scoreHistoryData.rows.clear();
		endRemoveRows();
	}
	if (columnCount >= 0)
	{
		beginRemoveColumns(QModelIndex(), 0, columnCount);
		m_scoreHistoryData.fieldGroup.clear();
		endRemoveColumns();
	}
}

QModelIndex ScoreHistoryModel::index(int row, int column, const QModelIndex& parent) const
{
	if (row >= 0 && column >= 0 && row < m_scoreHistoryData.rows.size())
	{
		pDbFieldGroup pRowData = m_scoreHistoryData.rows[row];
		if (pRowData != nullptr
			&& column < pRowData->fields.size())
		{
			return createIndex(row, column, &m_scoreHistoryData.rows[row]->fields[column]);
		}
	}
	return QModelIndex();
}

int ScoreHistoryModel::rowCount(const QModelIndex& parent) const
{
	return m_scoreHistoryData.rows.count();
}

int ScoreHistoryModel::columnCount(const QModelIndex& parent) const
{
	return m_scoreHistoryData.fieldGroup.fields.count();
}

QString ScoreHistoryModel::GetHorizontalHeaderName(int section) const
{
	return headerData(section, Qt::Horizontal, s_roleIndex_scoreHistory).toString();
}

QString ScoreHistoryModel::GetVerticalHeaderName(int section) const
{
	return headerData(section, Qt::Vertical, s_roleIndex_scoreHistory).toString();
}

QVariant ScoreHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (s_roleIndex_scoreHistory == role)
	{
		if (Qt::Horizontal == orientation)
		{
			if (section < m_scoreHistoryData.fieldGroup.fields.size())
			{
				return QVariant(QString::fromStdWString(L"%1").arg(m_scoreHistoryData.fieldGroup.fields[section].value()));
			}
		}
		else
		{
			/*QString aaa;
			if (section < m_scoreHistoryData.fieldGroup.fields.size())
			{
				aaa = QString::fromStdWString(L"%1").arg(m_scoreHistoryData.fieldGroup.fields[section].value());
			}
			return QVariant(QString("%1%2").arg(section + 1).arg(aaa));*/
			return QVariant(QString("%1").arg(section + 1));
		}
	}	
	return QVariant();
}

QVariant ScoreHistoryModel::data(const QModelIndex& index, int role) const
{
	if (index.isValid())
	{
		if (s_roleIndex_scoreHistory == role)
		{
			const auto rowIndex = index.row();
			const auto columnIndex = index.column();
			if (rowIndex >= 0 && columnIndex >= 0)
			{
				return m_scoreHistoryData.rows[index.row()]->fields[index.column()].value();
			}
		}
	}
	return QVariant();
}

Qt::ItemFlags ScoreHistoryModel::flags(const QModelIndex& index) const
{
	return Qt::NoItemFlags;
	//return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}

QHash<int, QByteArray> ScoreHistoryModel::roleNames() const
{
	return m_roleNames;
}

将自定义model实例对象添加到QML的根上下文属性

这里的model对象放进了TetrisBusiness类的私有成员中,所以调用了TetrisBusiness::getScoreHistoryModel()

核心代码:

engine.rootContext()->setContextProperty("scoreHistoryModelInstance", business.getScoreHistoryModel());

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QtQml>
#include "TetrisBusiness.h"
#include "ScoreHistoryModel.h"

QObject* qobject_singletonBusiness_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
{
	Q_UNUSED(engine);
	Q_UNUSED(scriptEngine);
	return new TetrisBusiness();
}

int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QGuiApplication app(argc, argv);

    //注册单例类型-业务逻辑类
    qmlRegisterSingletonType<TetrisBusiness>("Yih.Tetris.Business", 1, 0, "TetrisBusiness", qobject_singletonBusiness_provider); 

	QQmlApplicationEngine engine;
    TetrisBusiness business(engine.rootContext());
    engine.rootContext()->setContextProperty("scoreHistoryModelInstance", business.getScoreHistoryModel());
    engine.rootContext()->setContextProperty("businessInstance", &business);

    engine.load(QUrl(QStringLiteral("qrc:/qt/qml/tetrisgamemain/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

MyTableView

MyTableView的介绍

关键点:

  • 左上角有个功能按钮,可自定义图片与按钮响应函数
  • 行表头内容和列表头内容通过调用回调函数实现(columnHeaderNameFunc、rowHeaderNameFunc)
  • 使用定时器进行表格布局刷新(tableView.forceLayout())
  • 在text文本高/宽改变槽函数中,调整表格和表头的高、宽
  • 使用Connections响应model发出的信号(数据更新、表头内容更新等)
  • 使用MouseArea实现表头的拖拽、滚动、行列高度宽度调整等功能
  • 在TableView的onContentXChanged、onContentYChanged中设置表头的x、y,实现表头对表格拖拽动作的跟随

TableView的基本使用:

//创建表格内单元格委托
//(这里做得比较简单,只是画了个矩形,上面显示文本,文本内容与model的role角色绑定)
    Component {
        id: tableCellDelegate
        Rectangle {
            id: tableCell
            implicitWidth: root.columnHeaderCellWidthMin
            implicitHeight: root.rowHeaderCellHeightMin
            color: "transparent"
            border.color: root.contentBorderColor
            border.width: 1
            clip: true

            Text {
                id: tableCellText
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
                text: role_display //文本内容与model的role角色绑定)
                color: root.contentTextColor
                font.weight: Font.Bold
                font.pointSize: root.textPointSize

                onContentHeightChanged: {
                    setRowHeightByTextHeight();
                }

                onContentWidthChanged: {
                    setColumnWidthByTextWidth();
                }
            }

            TableView.onReused: {
                setRowHeightByTextHeight();
                setColumnWidthByTextWidth();
            }
            。。。。
            。。。。
        }
    }


    //表格内容
    TableView {
        id: tableView
        anchors.fill: parent
        anchors.topMargin: columnHeaderArea.height
        anchors.leftMargin: rowHeaderArea.width
        rowSpacing: root.rowColumnSpacing
        columnSpacing: root.rowColumnSpacing

        //clip设置为true后,超出表格边界框的内容不会显示
        clip: true

        //reuseItems设置为true后,委托控件复用时,会调用委托中的TableView.onReused,具体可参考Qt帮助手册对TableView的介绍
        reuseItems: true  

        delegate: tableCellDelegate  //设置委托
        model: scoreHistoryModelInstance  //设置model

        //表格单元格的宽度、高度回调函数,columnWidths、rowHeights内容修改后,需要调用tableView.forceLayout()来刷新表格布局
        property var columnWidths: []
        columnWidthProvider: function (column) { return columnWidths[column] }
        property var rowHeights: []
        rowHeightProvider: function (row) { return rowHeights[row] }

        //使列表头(横向表头)跟随表格移动
        onContentXChanged: {
            columnHeaderItem.x = columnHeaderItem.originX - (tableView.contentX - tableView.originX);
        }

        //使行表头(列向表头)跟随表格移动
        onContentYChanged: {
            rowHeaderItem.y = rowHeaderItem.originY - (tableView.contentY - tableView.originY);
        }

        //这里的滚动条只用于显示,所以设置了enabled: false
        //垂直滚动条
        ScrollBar.vertical: ScrollBar {
            id: verticalScrollBar
            policy: ScrollBar.AsNeeded
            enabled: false
        }

        //水平滚动条
        ScrollBar.horizontal: ScrollBar {
            id: horizontalScrollBar
            policy: ScrollBar.AsNeeded
            enabled: false
        }
    }

MyTableView.qml源码

import QtQuick 2.13
import QtQuick.Controls 2.9

Rectangle {
    id: root
    height: 480
    width: 640
    color: "transparent"
    border.color: contentBorderColor
    border.width: 1
    clip: true
    property alias model: tableView.model
    property alias functionButton: functionButton

    property var columnHeaderNameFunc: null //列表头内容回调函数
    property var rowHeaderNameFunc: null //行表头内容回调函数

    property var rowHeaderCellWidthMin: 40 //行表头单元格宽度(最小值)
    property var rowHeaderCellHeightMin: 38 //行表头单元格高度(最小值)

    property var columnHeaderCellWidthMin: 40 //列表头单元格宽度(最小值)
    property var columnHeaderCellHeightMin: 40 //列表头单元格高度(最小值)

    property var rowColumnSpacing: 3 //行列间隙

    property var textPointSize: 20 //字体大小
    property var contentTextColor: "white" //表格内容字体颜色
    property var contentBorderColor: "darkorchid" //表格内容边框颜色
    property var rowHeaderTextColor: "gold" //行表头字体颜色
    property var rowHeaderBorderColor: "red" //行表头边框颜色
    property var columnHeaderTextColor: "cyan" //列表头字体颜色
    property var columnHeaderBorderColor: "green" //列表头边框颜色

    //左上角刷新按钮
    Item {
        x: 0
        y: 0
        width: rowHeaderArea.width
        height: columnHeaderArea.height
        ImageButton {
            id: functionButton
            anchors.centerIn: parent
            width: Math.min(parent.width, parent.height)
            height: Math.min(parent.width, parent.height)
        }
    }

    onWidthChanged: {
        console.log("root.onWidthChanged");
        refresh();
    }

    //列表头
    Rectangle { //亦可用Flickable实现
        id: columnHeaderArea
        x: rowHeaderArea.width
        y: 0
        width: root.width - rowHeaderArea.width
        height: columnHeaderItem.height
        color: "transparent"
        border.color: root.columnHeaderBorderColor
        border.width: 1
        clip: true

        Rectangle {
            id: columnHeaderItem
            x: originX
            y: 0
            width: columnHeaderRow.width
            height: root.columnHeaderCellHeightMin
            color: "transparent"
            clip: false
            property int originX: 0

            Row {
                id: columnHeaderRow
                spacing: root.rowColumnSpacing

                Repeater {
                    id: columnHeaderRepeater
                    model: tableView.columns > 0 ? tableView.columns : 0
                    property var cellHeightMax: 0

                    Rectangle {
                        id: columnHeaderCell
                        implicitWidth: root.columnHeaderCellWidthMin
                        implicitHeight: columnHeaderItem.height
                        color: "transparent"
                        border.color: root.columnHeaderBorderColor
                        border.width: 1
                        clip: false
                        property alias text: columnHeaderCellText.text

                        Text {
                            id: columnHeaderCellText
                            anchors.fill: parent
                            horizontalAlignment: Text.AlignHCenter
                            verticalAlignment: Text.AlignVCenter
                            text: root.columnHeaderNameFunc(index)
                            color: root.columnHeaderTextColor
                            font.weight: Font.Bold
                            font.pointSize: root.textPointSize
                            clip: true

                            onContentWidthChanged: {
                                var curWidth = columnHeaderCellText.contentWidth;
                                if (curWidth < root.columnHeaderCellWidthMin || curWidth === columnHeaderCell.width) {
                                    return;
                                }
                                root.setColumnWidth(index, curWidth);
                            }

                            onContentHeightChanged: {
                                columnHeaderRepeater.cellHeightMax = Math.max(columnHeaderCellText.contentHeight, columnHeaderRepeater.cellHeightMax);
                                if (columnHeaderRepeater.cellHeightMax > columnHeaderItem.height) {
                                    columnHeaderItem.height = columnHeaderRepeater.cellHeightMax;
                                }
                                else {
                                    columnHeaderItem.height = Math.max(columnHeaderRepeater.cellHeightMax, root.columnHeaderCellHeightMin);
                                }
                            }
                        }

                        //通过拖拽表头移动表格内容
                        MouseArea {
                            z: 1
                            anchors.fill: parent
                            hoverEnabled: false
                            property var pressedX: 0
                            property bool bPressAndHold: false

                            onPressed: {
                                pressedX = mouse.x;
                                bPressAndHold = true;
                            }

                            onReleased: {
                                if (bPressAndHold) {
                                    bPressAndHold = false;
                                    tableView.returnToBounds();
                                }
                            }

                            onMouseXChanged: {
                                if (bPressAndHold) {
                                    var offset = mouse.x-pressedX;
                                    moveContentX(offset);
                                }
                            }

                            onWheel: {
                                var offset = wheel.angleDelta.y / (8*15);
                                moveContentX(offset*15);
                                tableView.returnToBounds();
                            }

                            function moveContentX(offset) {
                                var tmp = tableView.contentX - offset;
                                //if (tmp >= tableView.originX) {
                                    tableView.contentX = tmp;
                                    tableView.forceLayout();
                                //}
                            }
                        }

                        MouseArea {
                            z: 2
                            width: root.rowColumnSpacing
                            height: columnHeaderCell.height
                            anchors.left: columnHeaderCell.right
                            cursorShape: Qt.SplitHCursor
                            hoverEnabled: false
                            property var pressedX: 0

                            onPressAndHold: {
                                pressedX = mouse.x;
                            }

                            onMouseXChanged: {
                                var offset = mouse.x-pressedX;
                                var curItem = columnHeaderRepeater.itemAt(index);
                                if (-curItem.width+root.columnHeaderCellWidthMin < offset) {
                                    root.adjustColumnWidth(index, offset);
                                    tableView.forceLayout();
                                }
                            }
                        }
                    }

                    onCountChanged: {
                        //表格单元格宽度数组初始化
                        tableView.columnWidths.length = 0;
                        for (var i=0; i<columnHeaderRepeater.count; i++) {
                            tableView.columnWidths.push(columnHeaderRepeater.itemAt(i).width);
                        }
                    }
                }
            }
        }
    }

    //行表头
    Rectangle { //亦可用Flickable实现
        id: rowHeaderArea
        x: 0
        y: columnHeaderArea.height
        width: rowHeaderItem.width
        height: root.height - columnHeaderArea.height
        color: "transparent"
        border.color: root.rowHeaderBorderColor
        border.width: 1
        clip: true

        Rectangle {
            id: rowHeaderItem
            x: 0
            y: originY
            width: root.rowHeaderCellWidthMin
            height: rowHeaderRow.height
            color: "transparent"
            clip: false
            property int originY: 0

            Column {
                id: rowHeaderRow
                spacing: root.rowColumnSpacing
                Repeater {
                    id: rowHeaderRepeater
                    model: tableView.rows > 0 ? tableView.rows : 0
                    property var cellWidthMax: 0

                    Rectangle {
                        id: rowHeaderCell
                        width: rowHeaderItem.width
                        height: root.rowHeaderCellHeightMin
                        color: "transparent"
                        border.color: root.rowHeaderBorderColor
                        border.width: 1
                        clip: false
                        property alias text: rowHeaderCellText.text

                        Text {
                            id: rowHeaderCellText
                            anchors.fill: parent
                            horizontalAlignment: Text.AlignHCenter
                            verticalAlignment: Text.AlignVCenter
                            text: root.rowHeaderNameFunc(index)
                            color: root.rowHeaderTextColor
                            font.weight: Font.Bold
                            font.pointSize: root.textPointSize
                            clip: true

                            onContentWidthChanged: {
                                rowHeaderRepeater.cellWidthMax = Math.max(rowHeaderCellText.contentWidth, rowHeaderRepeater.cellWidthMax);
                                if (rowHeaderRepeater.cellWidthMax > rowHeaderItem.width) {
                                    rowHeaderItem.width = rowHeaderRepeater.cellWidthMax;
                                }
                                else {
                                    rowHeaderItem.width = Math.max(rowHeaderRepeater.cellWidthMax, root.rowHeaderCellWidthMin);
                                }
                            }

                            onContentHeightChanged: {
                                var curheight = rowHeaderCellText.contentHeight;
                                if (curheight < root.rowHeaderCellHeightMin || rowHeaderCell.height === curheight) {
                                    return;
                                }
                                root.setRowHeight(index, curheight);
                            }
                        }

                        //通过拖拽表头移动表格内容
                        MouseArea {
                            z: 1
                            anchors.fill: parent
                            hoverEnabled: false
                            property var pressedY: 0
                            property bool bPressAndHold: false

                            onPressed: {
                                pressedY = mouse.y;
                                bPressAndHold = true;
                            }

                            onReleased: {
                                if (bPressAndHold) {
                                    bPressAndHold = false;
                                    tableView.returnToBounds();
                                }
                            }

                            onMouseXChanged: {
                                if (bPressAndHold) {
                                    var offset = mouse.y-pressedY;
                                    moveContentY(offset);
                                }
                            }

                            onWheel: {
                                var offset = wheel.angleDelta.y / (8*15);
                                moveContentY(offset*15);
                                tableView.returnToBounds();
                            }

                            function moveContentY(offset) {
                                var tmp = tableView.contentY - offset;
                                //if (tmp >= tableView.originY) {
                                    tableView.contentY = tmp;
                                    tableView.forceLayout();
                                //}
                            }
                        }

                        MouseArea {
                            z: 2
                            width: rowHeaderCell.width
                            height: root.rowColumnSpacing
                            anchors.top: rowHeaderCell.bottom
                            cursorShape: Qt.SplitVCursor
                            hoverEnabled: false
                            property var pressedY: 0

                            onPressAndHold: {
                                pressedY = mouse.y;
                            }

                            onMouseYChanged: {
                                var offset = mouse.y-pressedY;
                                var curItem = rowHeaderRepeater.itemAt(index);
                                if (-curItem.height+root.rowHeaderCellHeightMin < offset) {
                                    root.adjustRowHeight(index, offset);
                                    tableView.forceLayout();
                                }
                            }
                        }
                    }

                    onCountChanged: {
                        //表格单元格高度数组初始化
                        tableView.rowHeights.length = 0;
                        for (var i=0; i<rowHeaderRepeater.count; i++) {
                            tableView.rowHeights.push(rowHeaderRepeater.itemAt(i).height);
                        }
                    }
                }
            }
       }
    }

    //表格内单元格委托
    Component {
        id: tableCellDelegate
        Rectangle {
            id: tableCell
            implicitWidth: root.columnHeaderCellWidthMin
            implicitHeight: root.rowHeaderCellHeightMin
            color: "transparent"
            border.color: root.contentBorderColor
            border.width: 1
            clip: true

            Text {
                id: tableCellText
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
                text: role_display //"%1".arg(model.row)
                color: root.contentTextColor
                font.weight: Font.Bold
                font.pointSize: root.textPointSize

                onContentHeightChanged: {
                    setRowHeightByTextHeight();
                }

                onContentWidthChanged: {
                    setColumnWidthByTextWidth();
                }
            }

            TableView.onReused: {
                setRowHeightByTextHeight();
                setColumnWidthByTextWidth();
            }

            //单元格高度随内容调整(由小变大),不能由大变小,不然手动拖拽拉大后,又会自动变回文本的高度
            function setRowHeightByTextHeight() {
                var index = model.row;
                if (index < 0) {
                    return;
                }
                var curHeight = tableCellText.contentHeight;
                if (curHeight > root.rowHeaderCellHeightMin
                        && curHeight > tableView.rowHeights[index]) {
                    setRowHeight(index, curHeight);
                }
            }

            //单元格宽度随内容调整(由小变大),不能由大变小,不然手动拖拽拉大后,又会自动变回文本的宽度
            function setColumnWidthByTextWidth() {
                var index = model.column;
                if (index < 0) {
                    return;
                }

                var curWidth = tableCellText.contentWidth;
                if (curWidth > root.columnHeaderCellWidthMin
                        && curWidth > tableView.columnWidths[index]) {
                    setColumnWidth(index, curWidth);
                }

                if (tableView.columnWidths.length-1 === index) {
                    stretchTableWidth(); //利用右侧空白部分扩宽各行的宽度
                }
            }
        }
    }

    //表格内容
    TableView {
        id: tableView
        anchors.fill: parent
        anchors.topMargin: columnHeaderArea.height
        anchors.leftMargin: rowHeaderArea.width
        rowSpacing: root.rowColumnSpacing
        columnSpacing: root.rowColumnSpacing
        clip: true
        reuseItems: true
        delegate: tableCellDelegate
        model: null

        property var columnWidths: []
        columnWidthProvider: function (column) { return columnWidths[column] }
        property var rowHeights: []
        rowHeightProvider: function (row) { return rowHeights[row] }

        onContentXChanged: {
            columnHeaderItem.x = columnHeaderItem.originX - (tableView.contentX - tableView.originX);
        }

        onContentYChanged: {
            rowHeaderItem.y = rowHeaderItem.originY - (tableView.contentY - tableView.originY);
        }

        ScrollBar.vertical: ScrollBar {
            id: verticalScrollBar
            policy: ScrollBar.AsNeeded
            enabled: false
        }

        ScrollBar.horizontal: ScrollBar {
            id: horizontalScrollBar
            policy: ScrollBar.AsNeeded
            enabled: false
        }
    }

    Connections {
        target: tableView.model

        onDataChanged: {
            //void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
            root.refresh();
            console.log("onDataChanged");
        }

        onHeaderDataChanged: {
            //void headerDataChanged(Qt::Orientation orientation, int first, int last);
            console.log("onHeaderDataChanged");
            root.refresh();
        }

        onAllDataUpdated: {
            console.log("onAllDataUpdated");
            root.refresh();
        }
    }

    function setColumnWidth(index, width) {
        if (index < 0) {
            return;
        }

        if (index < columnHeaderRepeater.count) {
            columnHeaderRepeater.itemAt(index).width = width; //列表头宽度修改
        }

        if (index < tableView.columnWidths.length) {
            tableView.columnWidths[index] = width; //表格内容宽度修改
        }
    }

    function adjustColumnWidth(index, offsetWidth) {
        if (index < 0) {
            return;
        }

        if (index < columnHeaderRepeater.count) {
            columnHeaderRepeater.itemAt(index).width += offsetWidth; //表头宽度
        }

        if (index < tableView.columnWidths.length) {
            tableView.columnWidths[index] += offsetWidth; //内容宽度
        }
    }

    function setRowHeight(index, height) {
        if (index < 0) {
            return;
        }

        if (index < rowHeaderRepeater.count) {
            rowHeaderRepeater.itemAt(index).height = height; //行表头高度修改
        }

        if (index < tableView.rowHeights.length) {
            tableView.rowHeights[index] = height; //表格内容高度修改
        }
    }

    function adjustRowHeight(index, offsetHeight) {
        if (index < 0) {
            return;
        }

        if (index < rowHeaderRepeater.count) {
            rowHeaderRepeater.itemAt(index).height += offsetHeight;
        }

        if (index < tableView.rowHeights.length) {
            tableView.rowHeights[index] += offsetHeight;
        }
    }

    //利用右侧空白部分扩宽各行的宽度
    function stretchTableWidth() {
        var newContentWidth = 0;
        //for (var i=0; i<columnHeaderRepeater.count; i++) {
        for (var i in tableView.columnWidths) {
            newContentWidth += tableView.columnWidths[i]; //columnHeaderRepeater.itemAt(i).width;
            newContentWidth += tableView.columnSpacing;
        }
        newContentWidth -= tableView.columnSpacing;
        var areaWidth = root.width - rowHeaderArea.width;
        if (newContentWidth < areaWidth && tableView.columnWidths.length > 0) {
            var offset = (areaWidth - newContentWidth) / tableView.columnWidths.length;
            for (var j in tableView.columnWidths) {
                adjustColumnWidth(j, offset);
            }
        }
    }

    function updateColumnHeaderText() {
        columnHeaderRepeater.cellHeightMax = 0;
        for (var i=0; i<columnHeaderRepeater.count; i++) { //仅更新列表头内容,表头布局会随内容变化而更新
            columnHeaderRepeater.itemAt(i).text = "";
            columnHeaderRepeater.itemAt(i).text = root.columnHeaderNameFunc(i);
        }
    }

    function updateRowHeaderText() {
        rowHeaderRepeater.cellWidthMax = 0;
        for (var i=0; i<rowHeaderRepeater.count; i++) { //仅更新列表头内容,表头布局会随内容变化而更新
            rowHeaderRepeater.itemAt(i).text = "";
            rowHeaderRepeater.itemAt(i).text = root.rowHeaderNameFunc(i);
        }
    }


    //刷新
    function refresh() {
        console.log("refresh---");

        //表头内容更新
        updateRowHeaderText();
        updateColumnHeaderText();

        //刷新表格布局
        tableView.contentX = 0;
        tableView.contentY = 0;
        tableView.forceLayout();

        stretchTableWidth();
        tableView.forceLayout();
        tableView.returnToBounds();
    }

    Timer {
        id: forceLayoutTimer
        interval: 200
        running: false
        repeat: true
        onTriggered: {
            if (tableView !== null) {
                //为了避免警告:forceLayout(): Cannot do an immediate re-layout during an ongoing layout!
                //将forceLayout放在定时器中调用
                tableView.forceLayout();
            }
        }
    }
    onVisibleChanged: { //控件重新显示时,启动定时器进行表格布局刷新
        forceLayoutTimer.running = root.visible;
    }
}

MyTableView的使用

MyTableView {
            id: scoreHistoryTable
            width: scoreView.width * 0.9
            height: scoreView.height * 0.6
            model: scoreHistoryModelInstance
            functionButton.imageSource: "qrc:/img/refresh.png"
            functionButton.mouseArea.onClicked: {
                scoreHistoryTable.refresh();
                businessInstance.changeScoreHistoryData();
            }
            columnHeaderNameFunc: function (index) {
                return scoreHistoryTable.model.headerData(index, Qt.Horizontal, Qt.DisplayRole);
            }
            rowHeaderNameFunc: function (index) {
                return scoreHistoryTable.model.headerData(index, Qt.Vertical, Qt.DisplayRole);
            }
}

源码下载

源码下载点击这里:https://download.csdn.net/download/Jane_Yih/88849878

如果发现错误或有更好的想法,欢迎评论指出,谢谢!

  • 22
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值