向QTableView、QTreeView单元格插入窗体小部件的功能实现

2023-10-17 23:30

本文主要是介绍向QTableView、QTreeView单元格插入窗体小部件的功能实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.前言

2.问题的提出

3.预备知识说明

4.功能实现

4.1.说明

4.2.代码剖析

4.2.1.QTableView有关的几个尺寸及对象说明

4.2.2.createCtrl函数分析

4.2.2.updateCtlGeometry函数分析

4.2.3.setCellWndVisible函数分析

4.3.实现的完整效果

4.4.利用QAbstractItemView类的setIndexWidget函数实现


1.前言

我们知道:QTableWidget类有如下函数:

void QTableWidget::setCellWidget(int row, int column, QWidget *widget)

可以实现在指定的单元格插入窗体部件QWidget对象,如下代码:

setCellWidget(row, column, new QLineEdit);
setCellWidget(row, column + 1, new QTextEdit);

实现向第row行的column列和column+1列分别插入了一个QLineEdit和一个QTextEdit窗体小部件。

2.问题的提出

      公司现有代码是用QTableView实现的,改成QTableWidget比较麻烦或改为QTableWidget不能满足需求,但需要向QTableView的单元格插入窗体小部件,而QTableView貌似是没有QTableWidget类的setCellWidget类似函数的,如何实现?

补充说明:在写本篇博文的时候,我还不知道QAbstractItemView类有setIndexWidget函数实现了QTableWidget类的setCellWidget类似功能:

void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget)

 所以就写代码自己实现了向单元格插入窗体小部件功能,在第4.4节,我会详细说明该函数用法。(2022年12月1日补记)

3.预备知识说明

        因为QTableView是基于Qt的model/view framework框架的,所以读者要能看懂后面的功能点是如何实现的,需要具备Qt的model/view framework知识,即Qt的模型/视图框架知识。如果不懂这方面的知识,请在Qt Assistant 中输入Model/View Programming 学习了解。读者本机Qt安装目录下的Examples\Qt-XX.XX.XX\widgets\itemviews目录下有很多model/view framework的例子,可以进行自学了解,其中XX.XX.XX为Qt的版本号,如:5.14.1。

4.功能实现

4.1.说明

  • 限于篇幅的原因,后面的代码只贴出cpp文件代码,.h文件代码不贴出。
  • 如果读者想运行本例子,请自行根据cpp代码提取出.h文件的类声明和成员函数。
  • .cpp文件中的所有以m_开头的都是类的成员变量。
  • QtWidgetsApplication1是本例子的主程序。该类的m_pModel是CModel类型指针成员变量;m_vtBtnWnd、m_vtCheckBoxWnd都是类型为vector<QWidget*>容器;ui.tableView是QTableView类对象;m_nVertCurScrollValue是垂直滚动条滚动的当前值。

        为了给QTableView提供数据,必须实现一个模型即从QAbstractTableModel.类派生出自己的模型类来,模型类model.cpp代码如下:

#include "model.h"
CModel::CModel(QObject *parent): QAbstractTableModel(parent)
{}CModel::~CModel()
{}QVariant CModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const
{return QVariant();
}// 作为例子,假想QTableView有2列
int CModel::columnCount(const QModelIndex& parent/* = QModelIndex()*/) const
{return 2;
}// 作为例子,假想QTableView有88行
int CModel::rowCount(const QModelIndex& parent/* = QModelIndex()*/) const
{return 88;
}QVariant CModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{if (orientation != Qt::Horizontal) // 作为例子演示,我们只关心表头是水平的情况return QVariant();if (role != Qt::DisplayRole)// 作为例子演示,我们只关心 Qt::DisplayRolereturn QVariant();// 构造列,第1列列名为"button";第2列列名为"checkbox"if(0 == section)return "button";else if(1 == section)return "checkbox";return QVariant();
}

QtWidgetsApplication1.cpp实现如下: 

#include "QtWidgetsApplication1.h"
#include "model.h"
#include<QPushButton>
#include<QScrollBar>
#include<QCheckBox>
QtWidgetsApplication1::QtWidgetsApplication1(QWidget *parent): QWidget(parent)
{ui.setupUi(this);m_pModel = new CModel(this);ui.tableView->setModel(m_pModel);connect(ui.tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &QtWidgetsApplication1::headerSectionResized);   connect(ui.tableView->verticalScrollBar(), &QScrollBar::valueChanged, this, &QtWidgetsApplication1::viewScroll);createCtrl();}QtWidgetsApplication1::~QtWidgetsApplication1()
{}// 滚动条滚动
void QtWidgetsApplication1::viewScroll(int nValue)
{m_nVertCurScrollValue = nValue;updateCtlGeometry();
}// 按住鼠标左键,拖动表头列和列之间的分割线,从而改变列宽
void QtWidgetsApplication1::headerSectionResized(int logicalIndex, int oldSize, int newSize)
{updateCtlGeometry();
}
void QtWidgetsApplication1::createCtrl()
{auto nRowCount = m_pModel->rowCount();auto nColCount = m_pModel->columnCount();auto lastAllRowsTotalHeight = 0; // 统计本行之前的所有行高度总和auto nHorHeaderHeight = ui.tableView->horizontalHeader()->height();   // 水平表头列高度auto nVerticalHeaderWidth = ui.tableView->verticalHeader()->width();  // 垂直表头列宽度auto nTableViewFrameWidth = ui.tableView->frameWidth();               // QTableView对象边框宽度auto vertHeaderAndFrameWidth = nVerticalHeaderWidth + nTableViewFrameWidth; // 垂直表头列宽度和QTableView对象边框宽度之和auto horzHeaderAndFrameWidth = nHorHeaderHeight + nTableViewFrameWidth;     // 水平表头列高度和QTableView对象边框宽度之和for (auto nRowIndex = 0; nRowIndex < nRowCount; ++nRowIndex){auto qsText(QString("button%1").arg(nRowIndex));auto pBtnWnd = new QPushButton(qsText, ui.tableView);m_vtBtnWnd.push_back(pBtnWnd);qsText = (QString("checkbox%1").arg(nRowIndex));auto pCheckBoxWnd = new QCheckBox(qsText, ui.tableView);m_vtCheckBoxWnd.push_back(pCheckBoxWnd);auto nCurRowHeight = ui.tableView->rowHeight(nRowIndex);  // 当前行高if (0 != nRowIndex){auto lastRowIndex = (nRowIndex - 1);lastAllRowsTotalHeight += ui.tableView->rowHeight(lastRowIndex); // 累计本行之前的所有行高的和pBtnWnd->setGeometry(vertHeaderAndFrameWidth,horzHeaderAndFrameWidth + lastAllRowsTotalHeight, ui.tableView->columnWidth(0),nCurRowHeight);pCheckBoxWnd->setGeometry(vertHeaderAndFrameWidth + ui.tableView->columnWidth(0),horzHeaderAndFrameWidth + lastAllRowsTotalHeight, ui.tableView->columnWidth(1),nCurRowHeight);}else// 第一行要单独处理,第一行没有上一行,即nRowIndex - 1不存在,是负值{pBtnWnd->setGeometry(vertHeaderAndFrameWidth, horzHeaderAndFrameWidth, ui.tableView->columnWidth(0), nCurRowHeight);pCheckBoxWnd->setGeometry(vertHeaderAndFrameWidth + ui.tableView->columnWidth(0), horzHeaderAndFrameWidth, ui.tableView->columnWidth(1), nCurRowHeight);}}
}// 更新单元格窗体部件几何尺寸
void QtWidgetsApplication1::updateCtlGeometry()
{auto nRowCount = m_pModel->rowCount();auto lastAllRowsTotalHeight = 0; // 统计本行之前的所有行高度总和auto nHorHeaderHeight = ui.tableView->horizontalHeader()->height();   // 水平表头列高度auto nVerticalHeaderWidth = ui.tableView->verticalHeader()->width();  // 垂直表头列宽度auto nTableViewFrameWidth = ui.tableView->frameWidth();               // QTableView对象边框宽度auto vertHeaderAndFrameWidth = nVerticalHeaderWidth + nTableViewFrameWidth; // 垂直表头列宽度和QTableView对象边框宽度之和auto horzHeaderAndFrameWidth = nHorHeaderHeight + nTableViewFrameWidth;     // 水平表头列高度和QTableView对象边框宽度之和auto xPos = vertHeaderAndFrameWidth;// 滚动条没有达到最大值if (m_nVertCurScrollValue != ui.tableView->verticalScrollBar()->maximum()){for (auto nRowIndex = 0; nRowIndex < nRowCount; ++nRowIndex){auto pBtnWnd = m_vtBtnWnd[nRowIndex];auto pCheckBoxWnd = m_vtCheckBoxWnd[nRowIndex];auto nCurRowHeight = ui.tableView->rowHeight(nRowIndex); // 当前行的高度int yPos;if (0 != nRowIndex){auto lastRowIndex = (nRowIndex - 1);lastAllRowsTotalHeight += ui.tableView->rowHeight(lastRowIndex); // 统计本行之前的所有行的高度和/* m_nVertCurScrollValue值是以单元格高度为单位的,即每次垂直滚动距离是单元格倍数当鼠标单击滚动条向下滚动时,需要扣减 m_nCurScrollValue * ui.tableView->rowHeight(nRowIndex)距离*/yPos = horzHeaderAndFrameWidth + lastAllRowsTotalHeight - m_nVertCurScrollValue * ui.tableView->rowHeight(nRowIndex);pBtnWnd->setGeometry(xPos, yPos, ui.tableView->columnWidth(0), nCurRowHeight);// CheckBox的横坐标就是第1列QPushButton的x坐标再加上QPushButton的宽度pCheckBoxWnd->setGeometry(xPos + ui.tableView->columnWidth(0), yPos, ui.tableView->columnWidth(1), nCurRowHeight);}else // 第一行要单独处理,第一行没有上一行,即nRowIndex - 1不存在,是负值{yPos = horzHeaderAndFrameWidth - m_nVertCurScrollValue * ui.tableView->rowHeight(0);pBtnWnd->setGeometry(xPos, yPos, ui.tableView->columnWidth(0), nCurRowHeight);pCheckBoxWnd->setGeometry(xPos + ui.tableView->columnWidth(0), yPos, ui.tableView->columnWidth(1), nCurRowHeight);           }setCellWndVisible(pBtnWnd, pCheckBoxWnd, yPos, horzHeaderAndFrameWidth);} // end for}else // 拖动滚动条到达最大值要单独处理{auto nViewHeight = ui.tableView->viewport()->height();for (auto nRowIndex = nRowCount - 1; nRowIndex >= 0 ; --nRowIndex){auto pBtnWnd = m_vtBtnWnd[nRowIndex];auto pCheckBoxWnd = m_vtCheckBoxWnd[nRowIndex];lastAllRowsTotalHeight += ui.tableView->rowHeight(nRowIndex);int yPos = nViewHeight + nHorHeaderHeight - lastAllRowsTotalHeight;pBtnWnd->setGeometry(xPos, yPos, ui.tableView->columnWidth(0),ui.tableView->rowHeight(nRowIndex));pCheckBoxWnd->setGeometry(xPos + ui.tableView->columnWidth(0), yPos, ui.tableView->columnWidth(1),ui.tableView->rowHeight(nRowIndex));setCellWndVisible(pBtnWnd, pCheckBoxWnd, yPos, horzHeaderAndFrameWidth);}}
}// 设置单元格窗体部件的可见性
void QtWidgetsApplication1::setCellWndVisible(QWidget* pBtnWnd, QWidget* pCheckBoxWnd,int yPos, int nHorzHeaderAndFrameWidth)
{/* 如果单元格所在窗体部件的纵坐标比水平表头高度与QTableView的上边框之和还小,就隐藏窗体部件,防止向上拖动滚动条到最顶部时,最上面一行单元格所在窗体部件遮挡住水平表头*/ if (yPos < nHorzHeaderAndFrameWidth) {pBtnWnd->hide();pCheckBoxWnd->hide();}else // 这种情况是:向下拖动滚动条时,最上面一行单元格所在窗体部件在水平表头下方,即没遮挡住水平表头{pBtnWnd->show();pCheckBoxWnd->show();}
}void QtWidgetsApplication1::resizeEvent(QResizeEvent* event)
{QWidget::resizeEvent(event);updateCtlGeometry();
}

4.2.代码剖析

4.2.1.QTableView有关的几个尺寸及对象说明

 图1

QTableView类对象内部有个viewport对象,可通过如下代码获得:

ui.tableView->viewport();

 viewport对象在QTableView类对象中的位置如下蓝色方框所示:

图2 

 即viewport对象是QTableView类对象剔除边框、垂直表头、水平表头的区域。

4.2.2.createCtrl函数分析

第39行:模型m_pModel获取QTableView的有多少行。

第50~79行:每行的第1列创建一个QPushButton;每行的第2列创建一个QCheckBox。其思路是:

  • 根据QTableView的竖向表头列宽度和QTableView的边框宽(厚)度和单元格宽度(列宽)算出QPushButton、QCheckBox窗体部件的x坐标。
  • 根据QTableView的水平表头列宽度和QTableView的边框宽(厚)度和当前行之前的所有单元格行高总值算出QPushButton、QCheckBox窗体部件的y坐标。
  • 因为第1行没有上一行,即nRowIndex - 1不存在,是负值,所有第1行要单独处理。
  • 然后调用setGeometry函数设置QPushButton、QCheckBox窗体部件的位置和宽高,这样QPushButton、QCheckBox窗体部件正好在每行的每个单元格的位置上了。

结合图1,就能很好理解createCtrl函数的nHorHeaderHeight、nVerticalHeaderWidth、nTableViewFrameWidth的含义了。

4.2.2.updateCtlGeometry函数分析

updateCtlGeometry函数在垂直滚动条滚动、按住鼠标左键,拖动水平表头列和列之间的分割线,从而改变列宽、窗体大小变化时会被调用。该函数和createCtrl函数思想类似,但考虑了:

  • 垂直滚动条滚动的情况。m_nVertCurScrollValue值表示垂直滚动条当前的值,该值是以单元格高度为单位的,即每次垂直滚动条滚动的距离是单元格倍数。当鼠标单击滚动条向下滚动时,需要扣减 m_nCurScrollValue * ui.tableView->rowHeight(nRowIndex)距离,此时呈现的视觉效果是单元格及单元格内的QPushButton、QCheckButton向上移动。
    垂直滚动条滚动到最大值时要单独处理,处理代码如129~147行所示。如果不单独处理,则当滚动条达到最大位置时,则有可能出现:1)最后一行单元格和底部不是严格封合的,即留有空隙;2):最后一行不能完全显示。3):当改变窗体大小(如:最大化)时,单元格中的QPushButton、QCheckButton消失了。如下图所示:

图3

 图4

              垂直滚动条滚动到最大位置单独处理的思路是:通过获取QTableView的viewport()高度(参见图2),按照行索引从大到小并从viewport()的底部向上设置行单元格的QPushButton、QCheckButton位置及几何尺寸。

4.2.3.setCellWndVisible函数分析

setCellWndVisible函数设置单元格窗体部件的可见性。在向上滚动垂直滚动条滚动到最顶部时,有如下情况:

图5 

 可以看到,最上面第1行的单元格遮挡住水平表头了。该函数就是处理这种情况的,其思路是:

  • 如果单元格所在窗体部件的纵坐标比水平表头高度与QTableView的上边框之和还小,证明在水平表头上方靠近标题栏方向,就隐藏窗体部件,防止向上拖动滚动条到最顶部时,最上面一行单元格所在窗体部件遮挡住水平表头。
  • 而当向下拖动滚动条时,如果单元格所在窗体部件的纵坐标比水平表头高度与QTableView的上边框之和还大,则单元格所在窗体部件证明在水平表头的下方,就将其显示。

4.3.实现的完整效果

实现的完整效果如下:

图6 

4.4.利用QAbstractItemView类的setIndexWidget函数实现

       本小节是2022年12月1日补写的,前面章节是之前写的,在补写本节之前,我还不知道QAbstractItemView类有setIndexWidget函数实现了前几节的功能。

       因为QTreeView、QTableView是继承自QAbstractItemView类,所以QTreeView、QTableView类对象也能调用setIndexWidget函数,该函数声明如下:

void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget)

第1个参数是模型索引(注:模型索引涉及到了Qt的model/view framework框架,请自行查阅Qt帮助),你可以想象为就是一个单元格所在的行列索引号。如果该参数无效,例如是根节点索引时,则这个函数什么都不做。

第2个参数是单元格要插入的窗体部件。注意:窗体部件autoFillBackground 属性要设置为true,否则窗体背景色是透明的。如果在指定索引处的A窗体被后来的窗体B替换,则A窗体被删除,如下:

setIndexWidget(index, new QLineEdit);...setIndexWidget(index, new QTextEdit);

则QLineEdit会被删除。本函数仅仅能用来根据项的数据在可见区域显示一些静态内容,如果需要显示一些动态内容或者显示一个自定义的编辑框部件,需通过子类化QStyledItemDelegate类实现。如下代码可以实现上述同样(放在构造函数里)功能,且不需要updateCtlGeometry类似函数控制单元格窗体部件几何位置和尺寸。

void QtWidgetsApplication1::installWidget()
{auto nRowCount = m_pModel->rowCount();auto nColCount = m_pModel->columnCount();for (auto nRowIndx = 0; nRowIndx < nRowCount; ++nRowIndx){auto modelIndx = m_pModel->index(nRowIndx, 0);QString qsText(QString("button%1").arg(nRowIndx));ui.tableView->setIndexWidget(modelIndx, new QPushButton(qsText, this));modelIndx = m_pModel->index(nRowIndx, 1);qsText = (QString("checkbox%1").arg(nRowIndx));ui.tableView->setIndexWidget(modelIndx, new QCheckBox(qsText, this));}}

这篇关于向QTableView、QTreeView单元格插入窗体小部件的功能实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/228670

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现