1.前言
本博文所说的技术点适用于同时满足下面条件的所有视图类:
模型类从 QAbstractItemModel派生。
代理类从QStyledItemDelegate派生。
故本博文所说的技术点也适用于QTableView。
2.需求提出
基于Qt的model/view framework技术,利用QTreeView树视图实现业务,为QTreeView设置数据模型如下:
m_pLayerTreeView->setModel(m_pLayTreeModel);
其中为m_pLayerTreeView为QTreeView类对象;m_pLayTreeModel为CLayTreeModel类对象。CLayTreeModel类实现如下:
CLayTreeModel::CLayTreeModel(const CVectorLayerMap& mpLayer, QObject *parent)
: QAbstractItemModel(parent)
{
}
QVariant CLayTreeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
......... // 其它代码略
if (Qt::CheckStateRole == role) // 获取Qt::CheckStateRole角色
{
return pLayer->showOnBaseMap();
}
}
.........// 其它代码略
在上面代码中,通过获取Qt::CheckStateRole角色,树视图项中就会复选框出现,如下:
如果pLayer->showOnBaseMap()为true,则复选框呈勾选状态;反之则为取消勾选状态。
现在的问题是:如何实现鼠标单击勾选或取消勾选树视图中的复选框功能呢?
3.解决方法
构建一个从QStyledItemDelegate派生的代理类,将树视图对象的代理设置为该代理类对象:如下:
auto pLayerItemDelegate = new CLayerItemDelegate(this);
m_pLayerTreeView->setItemDelegate(pLayerItemDelegate);
CLayerItemDelegate类如下:
#include "layerItemDelegate.h"
#include<QDebug>
#include<QApplication>
#include<QMouseEvent>
CLayerItemDelegate::CLayerItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
CLayerItemDelegate::~CLayerItemDelegate()
{
}
bool CLayerItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
if (QEvent::MouseButtonPress != event->type()) // 不是鼠标单击事件,就返回
{
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
auto pMouseEvent = dynamic_cast<QMouseEvent*>(event);
if (Qt::LeftButton != pMouseEvent->button()) // 不是鼠标左键单击,就返回
{
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
auto pt = pMouseEvent->pos(); // 鼠标点击树项的位置点(相对于树视图左上角顶点)
auto itemRect = option.rect; // 该树项在树视图占据的矩形位置(矩形坐标是相对树视图左上角顶点)
auto nBtnMargin = qApp->style()->pixelMetric(QStyle::PM_ButtonMargin); // 复选框离该树项左侧或上侧的距离
auto pDefaultIndicatorWidth = qApp->style()->pixelMetric(QStyle::PM_ButtonDefaultIndicator);// 复选框默认边框厚(宽)度
auto nTwiceWidth = 2 * pDefaultIndicatorWidth; // 左右边框或上下边框,故需要乘2
// 复选框宽度为:复选框指示器宽度加复选框左右边框厚(宽)度
auto nIndicatorWidth = qApp->style()->pixelMetric(QStyle::PM_IndicatorWidth) + nTwiceWidth; // 复选框宽度
// 复选框高度为:复选框指示器高度加复选框上下边框厚(宽)度
auto nIndicatorHeight = qApp->style()->pixelMetric(QStyle::PM_IndicatorHeight) + nTwiceWidth; // 复选框高度
auto nIndicatorTopX = itemRect.left() + nBtnMargin; // 复选框相对树项左上角的x坐标
auto nIndicatorTopY = itemRect.top() + nBtnMargin; // 复选框相对树项左上角的y坐标
QRect indicatorRect(nIndicatorTopX, nIndicatorTopY, nIndicatorWidth, nIndicatorHeight); // 复选框在该树项占据的矩形
auto bPtInRect = indicatorRect.contains(pt); // 鼠标在复选框占据的矩形内单击
if (bPtInRect)
{
layerCheckBoxClickedSignal(index);
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
其中layerCheckBoxClickedSignal子定义的信号,参数为鼠标单击项的索引:
// 复选框被单击信号
void layerCheckBoxClickedSignal(const QModelIndex& index);
上面代码重载了editorEvent方法,当鼠标在树视图项上单击时,会进入到该函数,然后算出复选框在该树项的位置,看看鼠标单击位置是不是落在复选框在该树项占据的矩形内。如果是,就发送 layerCheckBoxClickedSignal信号,外层调用方捕捉该信号,在槽函数里将CLayTreeModel类data函数中影响Qt::CheckStateRole的布尔量设置为真或假,就可实现复选框的勾选或取消勾选。如下为代码:
connect(pLayerItemDelegate, &CLayerItemDelegate::layerCheckBoxClickedSignal, this, &CExquisiteGIS::layerCheckBoxClickedSlot);
void CExquisiteGIS::layerCheckBoxClickedSlot(const QModelIndex& index)
{
// 获取该树项绑定的CVectorLayer类对象的指针
auto pLayer = static_cast<CVectorLayer*>(index.internalPointer());
if (nullptr == pLayer)
{
return;
}
// 调用该类对象的方法
auto bShowBaseMap = pLayer->showOnBaseMap();
pLayer->setShowOnBaseMap(!bShowBaseMap);
// 注意:一定要刷新,否则有时复选框勾选和取消勾选不能同步,即勾选(取消勾选)了依然没勾选(取消勾选)
m_pLayerTreeView->update();
}
注意:一定要刷新,否则有时复选框勾选和取消勾选不能同步,即勾选(取消勾选)了依然没勾选(取消勾选)。当调用update()后会导致模型类CLayTreeModel的data函数被调用,从而实现界面复选框的刷新。
效果如下:
4.后记
上述功能,如果仅仅是为实现树视图,可以用QTreeWidget来实现。QTreeWidget类提供了对QCheckBox控件的函数和信号支持,不需要这么麻烦。另外也可以通过向QTreeView的树项插入QCheckBox控件来实现,具体参见《向QTableView、QTreeView单元格插入窗体小部件的功能实现》博文。需要说明的是:个人认为通过插入QCheckBox控件来实现,不够优雅;同时因为创建QCheckBox控件需要耗费内存,当树项很多时,在内存占用上没有前文不插入QCheckBox控件的方法来得优雅。