目录
1.前言
2.实现详解
3.附加说明
3.1.窗体小部件相应鼠标事件的实现
3.2.普通窗体部件、复杂窗体部件绘制的不同说明
3.3.可绘制在单元格中的QStyleOption子类
工作中经常会遇到这样的需求:向QAbstractItemView子类如QTreeView、QTableView单元格插入窗体小部件,如:进度条、按钮、单行编辑框等。下面链接的系列博文就是讲解如何实现该功能的。
《向QAbstractItemView子类如:QTreeView、QTableView等子项单元格插入窗体小部件的功能实现(第1种方法)》。
《向QAbstractItemView子类如:QTreeView、QTableView等子项单元格插入窗体小部件的功能实现(第2种方法)》。
《向QAbstractItemView子类如:QTreeView、QTableView等子项单元格插入窗体小部件的功能实现(第3种方法)》。
这些系列博文所说的技术点适用于同时满足下面条件的所有类:
模型类从 QAbstractItemModel派生。
代理类从QStyledItemDelegate或QItemDelegate派生。
视图类是QAbstractItemView的子类。
这些系列博文用到了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。
因为QColumnView、QHeaderView、QListView、QTableView、QTreeView、QListWidget 、QUndoView、QTableWidget、QTreeWidget都是从QAbstractItemView继承,故上面链接的博文所说的技术点也适用于这些类。
本博文通过Qt的QStyledItemDelegate或QItemDelegate类再结合QStyle类相关函数如:drawControl来实现向视图单元格插入窗体小部件功能。
说明:下述所贴源码的.h文件请从cpp文件中自己抠出,不再贴.h文件。
以表格视图为例说明,其它从QAbstractItemView派生的子类视图和表格视图类似。先实现表格视图类的数据模型类,如下为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有3列
int CModel::columnCount(const QModelIndex& parent/* = QModelIndex()*/) const
{
return 3;
}
// 作为例子,假想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::DisplayRole
return QVariant();
// 构造列,第1列列名为"button";第2列列名为"checkbox",第3列列名为"Slider"
if (0 == section)
return "button";
else if (1 == section)
return "checkbox";
else if (2 == section)
return "Slider";
return QVariant();
}
再实现表格视图的代理类,如下:
#include "tvItemDelegate.h"
#include
#include
CTVItemDelegate::CTVItemDelegate(QObject* parent /*= nullptr*/)
{
}
CTVItemDelegate::~CTVItemDelegate()
{
}
void CTVItemDelegate::drawPushButton(QPainter* painter, const QStyleOptionViewItem& option, const QString&qsWndText) const
{
QStyleOptionButton styleOptBtn;
styleOptBtn.rect = option.rect; // 设置按钮占据的矩形
styleOptBtn.icon = qApp->style()->standardIcon(QStyle::SP_DesktopIcon); // 设置按钮图标
styleOptBtn.iconSize = QSize(32, 32);// 设置按钮图标尺寸
styleOptBtn.text = QString("button%1").arg(qsWndText);// 设置按钮标题
styleOptBtn.state = QStyle::State_Enabled | QStyle::State_Raised; // 设置按钮状态
styleOptBtn.direction = Qt::LeftToRight; // 设置按钮水平布局,如果改为Qt::RightToLeft,则按钮图标在按钮标题右侧。
styleOptBtn.features = QStyleOptionButton::None | QStyleOptionButton::Flat;// 设置按钮风格特点为普通扁平按钮
qApp->style()->drawControl(QStyle::CE_PushButton, &styleOptBtn, painter); // 绘制按钮
}
void CTVItemDelegate::drawCheckBox(QPainter* painter, const QStyleOptionViewItem& option, const QString& qsWndText) const
{
QStyleOptionButton styleOptBtn;
styleOptBtn.rect = option.rect;// 设置按钮占据的矩形
styleOptBtn.icon = qApp->style()->standardIcon(QStyle::SP_DesktopIcon);// 设置按钮图标
styleOptBtn.iconSize = QSize(32, 32);
styleOptBtn.text = QString("CheckBox%1").arg(qsWndText);// 设置复选按钮标题
styleOptBtn.state = QStyle::State_Enabled | QStyle::State_Raised;// 设置按钮状态
styleOptBtn.direction = Qt::LeftToRight; // 设置按钮水平布局,如果改为Qt::RightToLeft,则按钮图标在按钮标题右侧。
styleOptBtn.features = QStyleOptionButton::None | QStyleOptionButton::Flat;// 设置按钮风格特点为普通扁平按钮
qApp->style()->drawControl(QStyle::CE_CheckBox, &styleOptBtn, painter);// 绘制按钮
}
void CTVItemDelegate::drawSlider(QPainter* painter, const QStyleOptionViewItem& option) const
{
QStyleOptionSlider styleOptnSlider;
styleOptnSlider.rect = option.rect;// 设置按钮占据的矩形
styleOptnSlider.state = QStyle::State_Enabled;// 设置按钮状态
styleOptnSlider.minimum = 0; // 设置滑块最小值
styleOptnSlider.maximum = 100;// 设置滑块最大值
styleOptnSlider.sliderPosition = 50;// 设置滑块当前值
qApp->style()->drawComplexControl(QStyle::CC_Slider, &styleOptnSlider, painter); // 绘制滑杆控件
}
void CTVItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (!index.isValid())
{
return;
}
auto nRowIndex = index.row();
auto nColIndex = index.column();
auto wndText = QString("%1").arg(nRowIndex);
switch (nColIndex)
{
case 0:
{
drawPushButton(painter, option, wndText);
}
break;
case 1:
{
drawCheckBox(painter, option, wndText);
}
break;
case 2:
{
drawSlider(painter, option);
}
break;
default:
break;
}
}
最后将设置表格视图模型、代理:
#include "QtWidgetsApplication1.h"
#include "model.h"
#include "tvItemDelegate.h"
QtWidgetsApplication1::QtWidgetsApplication1(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
auto pModel = new CModel(this);
// 为表视图设置数据模型
ui.tableView->setModel(pModel);
// 为表视图设置代理类
ui.tableView->setItemDelegate(new CTVItemDelegate(this));
ui.tableView->setColumnWidth(0, 200);
ui.tableView->setColumnWidth(1, 200);
ui.tableView->setColumnWidth(2, 200);
}
效果如下:
上述只是实现了插入窗体小部件,但发现按钮、滑杆等鼠标单击都没反应。这可以自己来实现,大体思路是:
重载代理类的如下函数:
[override virtual protected] bool QStyledItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
在editorEvent函数中的option参数中的option.rect判断出鼠标单击事件发生在哪个单元格,从而找到是哪个窗体小部件。
自定义一个信号,表示小部件被鼠标单击了。
在视图类中绑定3步骤中的自定义信号,并调用视图的update函数更新视图。
具体可参考《QAbstractItemView子类如:QTreeView、QTableView等子项单元格复选框勾选/取消勾选功能实现》。
上述可以看到普通窗体部件是通过drawControl函数绘制的;而复杂窗体控件是通过drawComplexControl函数绘制的。关于普通窗体部件、复杂窗体部件绘制的不同及这两个函数的含义,请参见《QStyle类用法总结(二)》博文的4.2、4.3节描述。QStyle::CC_Slider、QStyle::CE_CheckBox、QStyle::CE_PushButton各枚举值的含义,请参见《QStyle类用法总结(三)》中的2.2节或Qt Assist。
QStyleOption子类如下:
虽然说理论上这些子类都可以被绘制在视图的单元格中,但现实中往往由于单元格高度一般不是很高,再加上业务的需求,所以常用的为:QStyleOptionButton(按钮、复选按钮、单选按钮)、QStyleOptionComboBox(组合框), QStyleOptionGroupBox(组框) , QStyleOptionSlider(滑竿条), QStyleOptionSpinBox(上下翻值框), QStyleOptionToolButton(工具按钮)、 QStyleOptionFocusRect(带焦点的矩形)、QStyleOptionProgressBar(进度条)。