firemail
标题:
Model/View之子类化QAbstractItemModel实现QTreeView的复选框
[打印本页]
作者:
Qter
时间:
2020-3-24 18:34
标题:
Model/View之子类化QAbstractItemModel实现QTreeView的复选框
本帖最后由 Qter 于 2020-3-24 18:36 编辑
引言
先上效果图:
最近想要实现上图所示的一个数据展示列表,最先使用的QTreeWidget组件进行展示,但是遇到了当数据量过大(10000以上),第一次点击TabPage加载数据时,总是有很卡顿的感觉,得隔一段时间才能加载显示出数据。汗!偷懒偷不成了,效果自己都不能忍,更何况别人。因此使用了Model/View框架,自己实现了数据项和数据模型,最后效果还算满意。
需求是这样的,当点击表头时,可以全部选中或者全部不选中视图中的数据,而点击数据时,表头能展示选中状态的三态效果。配合键盘的Ctrl和Shift键,实现区域选中,多块选中(效果图见文章最后)
实现
一、 子类化QAbstractItemModel,自定义QTreeView的数据模型
QAbstractItemModel类是虚基类,子类化该类,得需要实现所有的纯虚函数才能实例化自定义的数据模型类。
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt:
isplayRole) const;
virtual bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);//设置数据
virtual Qt::ItemFlags flags(const QModelIndex & index) const;//返回Item项的可选,可用户点击等标识
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;//返回每个数据项的index
virtual QModelIndex parent(const QModelIndex &index) const;//本需求树只有一层,parent返回NULL
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt:
isplayRole) const;//返回相应role的表头数据
以上函数是为了实现本需求,必须实现的方法。可以说是自定义QTreeView模型的核心。
自定义Model头文件
#ifndef TREEVIEWMODEL_H
#define TREEVIEWMODEL_H
#include <QAbstractItemModel>
struct FlashIndexData{
FlashIndexData():is_be_checked(false){
}
bool is_be_checked;
quint32 unix_time;
quint16 addr;
};
class TreeViewModel:public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeViewModel(QObject *parent=NULL);
void setFlashData(QList<FlashIndexData> &flash_data);
void clear();
void getSelectedFlashData(QMap<quint32,quint16> &selected_list);
virtual int rowCount(const QModelIndex & parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt:
isplayRole) const;
virtual bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
virtual Qt::ItemFlags flags(const QModelIndex & index) const;
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex &index) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt:
isplayRole) const;
signals:
void stateChanged(Qt::CheckState state);
private slots:
void slot_stateChanged(Qt::CheckState state);
private:
QList<FlashIndexData> m_flash_index; //flash 索引
void onStateChanged();
enum{
CHECK_BOX_COLUMN = 0,
UNIX_TIME_COLUMN,
FLASH_ADDR_COLUMN
};
};
#endif // TREEVIEWMODEL_H
首先需要定义自己的数据容器,本需求的容器如下所示:
struct FlashIndexData{
FlashIndexData():is_be_checked(false){
}
bool is_be_checked;
quint32 unix_time;
quint16 addr;
};
返回数据源的个数,即 即将展示的数据的行数.列数固定,直接返回想要显示的列数即可。
int TreeViewModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_flash_index.count();
}
int TreeViewModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 3;
}
获取role的数据,设置role的数据
QVariant TreeViewModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
FlashIndexData index_data = m_flash_index.at(row);
switch (role) {
case Qt:
isplayRole:
if(column == UNIX_TIME_COLUMN)
return QDateTime::fromTime_t(index_data.unix_time).toString("yyyy-MM-dd hh:mm:ss");
else if(column == FLASH_ADDR_COLUMN)
return index_data.addr;
return "";
break;
case Qt::CheckStateRole:
if(column == CHECK_BOX_COLUMN)
return index_data.is_be_checked?Qt::Checked
t::Unchecked;
break;
case Qt::TextAlignmentRole:
if(column == CHECK_BOX_COLUMN)
return QVariant(Qt::AlignLeft|Qt::AlignVCenter);
else
return Qt::AlignCenter;
break;
case Qt::TextColorRole:
return QColor(Qt::black);
break;
case Qt::SizeHintRole:
return QSize(100,30);
break;
case Qt::FontRole:
return QFont("SimSun", 11);
break;
default:
break;
}
return QVariant();
}
//可编辑,只提供可选不可选的编辑,不提供对数据源的编辑
bool TreeViewModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(!index.isValid())
return false;
int column = index.column();
FlashIndexData index_data = m_flash_index.at(index.row());
switch (role) {
case Qt::UserRole: //根据表头的复选框选择
case Qt::UserRole+1: //根据鼠标点击
if(column == CHECK_BOX_COLUMN)
{
index_data.is_be_checked = (((Qt::CheckState)value.toInt()) == Qt::Checked);
m_flash_index.replace(index.row(),index_data);
emit dataChanged(index,index);
if(role == Qt::UserRole+1) //点击鼠标,更新表头复选框状态
onStateChanged();
return true;
}
break;
default:
return false;
break;
}
return false;
}
使Item显示复选框
//可选
Qt::ItemFlags TreeViewModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return QAbstractItemModel::flags(index);
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
if (index.column() == CHECK_BOX_COLUMN)
flags |= Qt::ItemIsUserCheckable;
return flags;
}
设置(row,column)的index
QModelIndex TreeViewModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column < 0 || column >= columnCount(parent))
return QModelIndex();
return createIndex(row,column);
}
只有一层树形结构,parent为空
QModelIndex TreeViewModel::parent(const QModelIndex &index) const
{
Q_UNUSED(index);
return QModelIndex();
}
返回表头的一些数据
QVariant TreeViewModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt:
isplayRole:
if(section == CHECK_BOX_COLUMN)
{
return QString("全选");
}else if(section == UNIX_TIME_COLUMN)
{
return QString("时间");
}else if(section == FLASH_ADDR_COLUMN)
{
return QString("地址");
}
return "";
break;
case Qt::FontRole:
return QFont("SimSun", 12);
break;
case Qt::TextAlignmentRole:
return Qt::AlignCenter;
break;
case Qt::TextColorRole:
return QColor(Qt::black);
break;
case Qt::SizeHintRole:
return QSize(100,40);
break;
case Qt::BackgroundRole:
return QBrush(Qt::black);
break;
default:
break;
}
return QVariant();
}
用于与表头HeaderView交互的信号和槽,可以协调表头和Item的复选框状态
void TreeViewModel::slot_stateChanged(Qt::CheckState state)
{
for(int i = 0;i < rowCount();++i)
{
setData(index(i,CHECK_BOX_COLUMN),state,Qt::UserRole);
}
}
void TreeViewModel:
nStateChanged()
{
int select_total = 0;
for(int i = 0;i < rowCount();++i)
{
if(m_flash_index.at(i).is_be_checked)
++select_total;
}
if(select_total == 0)
{
emit stateChanged(Qt::Unchecked);
}else if(select_total < rowCount())
{
emit stateChanged(Qt:
artiallyChecked);
}else
{
emit stateChanged(Qt::Checked);
}
}
用于更新Model和清空Model
TreeViewModel::TreeViewModel(QObject *parent):
QAbstractItemModel(parent)
{
m_flash_index.clear();
}
void TreeViewModel::setFlashData(QList<FlashIndexData> &flash_data)
{
m_flash_index = flash_data;
beginResetModel();
endResetModel();
emit stateChanged(Qt::Unchecked);
}
void TreeViewModel::clear()
{
m_flash_index.clear();
beginResetModel();
endResetModel();
emit stateChanged(Qt::Unchecked);
}
void TreeViewModel::getSelectedFlashData(QMap<quint32, quint16> &selected_list)
{
selected_list.clear();
for(int i = 0;i < rowCount();++i)
{
if(m_flash_index.at(i).is_be_checked)
{
selected_list.insert(m_flash_index.at(i).unix_time,m_flash_index.at(i).addr);
}
}
}
—————————————————————————–
二、 自定义QTreeView的HeaderView,实现表头复选框
头文件
#ifndef ATHEADERVIEW_H
#define ATHEADERVIEW_H
#include <QHeaderView>
class ATHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit ATHeaderView(QWidget *parent = 0);
~ATHeaderView(){}
protected:
virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
virtual void mousePressEvent(QMouseEvent *e);
virtual void mouseReleaseEvent(QMouseEvent *e);
signals:
void stateChanged(Qt::CheckState state);
private slots:
void slot_stateChanged(Qt::CheckState state);
private:
Qt::CheckState m_state;
bool m_is_pressed;
};
#endif // ATHEADERVIEW_H
实现表头背景的绘制,Section文本的绘制和复选框的绘制
//默认水平表头
ATHeaderView::ATHeaderView(QWidget *parent):
QHeaderView(Qt::Horizontal,parent)
{
m_state = Qt::Unchecked;
m_is_pressed = false;
setSectionsClickable(true);
}
void ATHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter,rect,logicalIndex);//绘制其他Section
painter->restore();
//绘制背景色
painter->save();
painter->setBrush(QBrush(Qt::gray));
painter->setPen(Qt::NoPen);
painter->drawRect(rect);
//绘制Section的Text
painter->setFont(QFont("SimSun", 12));
painter->setPen(QColor("#000000"));
painter->drawText(rect, Qt::AlignCenter, model()->headerData(logicalIndex,Qt::Horizontal).toString());
painter->restore();
//为第一列绘制Checkbox
if(logicalIndex == 0)
{
QStyleOptionButton option;
option.initFrom(this);
if(m_state == Qt::Unchecked)
{
option.state |= QStyle::State_Off;
}
else if(m_state == Qt:
artiallyChecked)
{
option.state |= QStyle::State_NoChange;
}
else if(m_state == Qt::Checked)
{
option.state |= QStyle::State_On;
}
option.iconSize = QSize(20, 20);
option.rect = QRect(QPoint(rect.left()+5,rect.top()+(rect.height()-20)/2),QPoint(rect.left()+25,rect.bottom()-(rect.height()-20)/2));
style()->drawPrimitive(QStyle:
E_IndicatorCheckBox, &option, painter);
}
}
void ATHeaderView::mousePressEvent(QMouseEvent *e)
{
int nColumn = logicalIndexAt(e->pos());
if ((e->buttons() & Qt:
eftButton) && (nColumn == 0))
{
m_is_pressed = true;
e->accept();
}
e->ignore();
}
void ATHeaderView::mouseReleaseEvent(QMouseEvent *e)
{
if(m_is_pressed)
{
if(m_state == Qt::Unchecked)
{
m_state = Qt::Checked;
}else
{
m_state = Qt::Unchecked;
}
updateSection(0);
emit stateChanged(m_state); //状态改变
}
m_is_pressed = false;
e->accept();
}
//根据Item的复选框状态,表头复选框状态更新
void ATHeaderView::slot_stateChanged(Qt::CheckState state)
{
m_state = state;
updateSection(0);
}
—————————————————————————–
三、使用Model和HeaderView,实现复选框的协调,同时选择Item,Model和HeaderView作出响应
初始化
m_headerView = new ATHeaderView(this);
m_treeModel = new TreeViewModel(this);
ui->treeView_flashindex->setModel(m_treeModel);
ui->treeView_flashindex->setHeader(m_headerView);
ui->treeView_flashindex->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->treeView_flashindex->setExpandsOnDoubleClick(false);
ui->treeView_flashindex->setIndentation(5);
ui->treeView_flashindex->setColumnWidth(0,150);
ui->treeView_flashindex->setColumnWidth(1,400);
ui->treeView_flashindex->header()->setStretchLastSection(true);
connect(m_headerView,SIGNAL(stateChanged(Qt::CheckState)),m_treeModel,SLOT(slot_stateChanged(Qt::CheckState)));
connect(m_treeModel,SIGNAL(stateChanged(Qt::CheckState)),m_headerView,SLOT(slot_stateChanged(Qt::CheckState)));
connect(ui->treeView_flashindex->selectionModel(),SIGNAL(selectionChanged(QItemSelection,QItemSelection)),this,SLOT(slot_selectionChanged(QItemSelection,QItemSelection)));
根据选中状态,多选或者单选,CheckBox作出响应
void NodeObjectFlashExport::slot_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
for(int i = 0;i < selected.indexes().count();++i)
{
m_treeModel->setData(selected.indexes().at(i),Qt::Checked,Qt::UserRole+1);
}
for(int i = 0;i < deselected.indexes().count();++i)
{
m_treeModel->setData(deselected.indexes().at(i),Qt::Unchecked,Qt::UserRole+1);
}
}
效果图如图所示:
点赞
————————————————
版权声明:本文为CSDN博主「sxpsxp12」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/sxpsxp12/article/details/73650333
欢迎光临 firemail (http://firemail.wang:8088/)
Powered by Discuz! X3