背景
QtQuick.Controls 1和QtQuick.Controls2的TableView在用法上有很大差异,而且Qt帮助手册对于QtQuick.Controls2的TableView表格行、列表头实现介绍不多。
因此,做了个自定义TableView控件(MyTableView),涉及 model/view、表头绘制、表头冻结拖拽、表头跟随表格内容移动、表格内容显示、拖拽表头调节表格行列宽度高度、滚动条显示等功能
一方面是提高对QML的熟练度,另一方面是以后其他项目也用得上,废话不多说,先上效果图。
效果展示
基本设计思路
- 自定义model C++类,继承QAbstractTableModel,实现几个必要的虚函数,如:rowCount()、columnCount()、roleNames()、data()等等(qt的model/view)
- 将自定义model C++类注册到QML中,或者创建一个实例对象添加为QML上下文数属性(C++与QML混合编程)
- 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
如果发现错误或有更好的想法,欢迎评论指出,谢谢!