Model/View之子类化QAbstractItemModel实现QTreeView的复选框

来源:互联网 发布:英翻汉软件 编辑:程序博客网 时间:2024/06/11 21:37

引言

  • 先上效果图:
  • 这里写图片描述

  • 最近想要实现上图所示的一个数据展示列表,最先使用的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::DisplayRole) 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;//返回每个数据项的indexvirtual QModelIndex parent(const QModelIndex &index) const;//本需求树只有一层,parent返回NULLvirtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) 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_OBJECTpublic:    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::DisplayRole) 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::DisplayRole) 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::DisplayRole:        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:Qt::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::DisplayRole:        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::onStateChanged(){    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::PartiallyChecked);    }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_OBJECTpublic:    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::PartiallyChecked)        {            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::PE_IndicatorCheckBox, &option, painter);    }}void ATHeaderView::mousePressEvent(QMouseEvent *e){    int nColumn = logicalIndexAt(e->pos());    if ((e->buttons() & Qt::LeftButton) && (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);    }}

效果图如图所示:
这里写图片描述

阅读全文
1 1
原创粉丝点击