Qt 可选树形结构
Qt 使用QTreeView实现可复选树
需求描述
我们经常可能有个需求就是:我们需要以树形结构去展示并选择某种数据,如下所示:
其中要求可以选择树上的节点,且当某节点的被选中后其子节点和父节点要随之联动。
实现分析
我不清楚Qt有没有这样现成的控件支持,据我所知可能没有(了解的人请多指教哟),所以我们可以简易实现一个,由于我们项目很多时候都是采用model+view的形式进行,所以我打算采取直接在QTreeView上做一些功夫:继承自QTreeView,为子类增添一些界面上的特性:即根据模型的选中状态随即改变模型中父子的选中关系。
注意,model+view的模式,是qt的模型视图框架,数据都被存在模型中,视图只是模型的一种呈现形式,是否可选存在于模型中,选中与未选中一个数据项被记录在了模型中,视图本身不记录这些信息,它只通过一些手段显示这些属性。
实现代码
我就简单的粘贴下代码吧,这只是一个快速实现的思路,有什么不妥烦请指教:
代码逻辑是,在为treeView设置模型时,我们连接一个模型数据更改的信号槽,一旦模型改变(节点设置为选中时会触发,当然其他数据改变也会触发,此处不做处理)就去刷新其刷新选中状态:
connect(model, SIGNAL(itemChanged(QStandardItem *)),
this, SLOT(onStandardItemChanged(QStandardItem *)));
具体怎么刷新模型选中状态可参考下面源码:
可选树视图头文件checkabletreeview.h:
#ifndef CHECKABLETREEVIEW_H
#define CHECKABLETREEVIEW_H
#include <QTreeView>
#include <QStandardItemModel>
/**
* @brief 三态联动可选树视图
* @author zhoumo
* @date 2021107 冬至 冷死
* @note 该类继承自QTreeView, 仅仅在QTreeView的基础上加了一个支持模型的可选联动功能
*/
class CheckableTreeView : public QTreeView
{
Q_OBJECT
public:
CheckableTreeView(QWidget *parent=0);
~CheckableTreeView();
/**
* @brief 设置树模型
* @param model 输入 待显示模型
*/
void setStandardItemModel(QStandardItemModel *model);
private slots:
/**
* @brief 模型中数据值改变调用槽
* @param item 输入 改变的item
*/
void onStandardItemChanged(QStandardItem *item);
private:
/**
* @brief 更新父节点状态
* @param item 输入 待更新父节点的子节点
*/
void updateParentState(QStandardItem *item);
};
#endif // CHECKABLETREEVIEW_H
可选树视图cpp文件checkabletreeview.cpp
#include "checkabletreeview.h"
#include <QDebug>
CheckableTreeView::CheckableTreeView(QWidget *parent):
QTreeView(parent)
{
}
CheckableTreeView::~CheckableTreeView()
{
}
void CheckableTreeView::setStandardItemModel(QStandardItemModel *model)
{
// 将获取model的改变来设置其父子间的变换关系
connect(model, SIGNAL(itemChanged(QStandardItem *)),
this, SLOT(onStandardItemChanged(QStandardItem *)));
this->setModel(model);
}
void CheckableTreeView::onStandardItemChanged(QStandardItem *item)
{
/************************
* 如果item checked
* 孩子全部checked
* 父亲检查后更新状态
* 如果item partly
* 什么也不做
* 父亲检查后更新状态
* 如果item unchecked
* 孩子全部都设置为unchecked
* 父亲检查后更新状态
* ***********************/
if(NULL != item){
// 更新孩子的状态
Qt::CheckState parentState = item->checkState();
int rowCount = -1;
if(Qt::Unchecked == parentState){
rowCount = item->rowCount();
for(int i=0; i<rowCount; ++i){
item->child(i)->setCheckState(Qt::Unchecked);
}
}else if(Qt::Checked == parentState){
rowCount = item->rowCount();
for(int i=0; i<rowCount; ++i){
item->child(i)->setCheckState(Qt::Checked);
}
}
// 更新父节点的状态
updateParentState(item);
}
}
void CheckableTreeView::updateParentState(QStandardItem *item)
{
/***************************************************
* 全部checked 则父节点设置为Checked
* 部分checked 或 存在PartiallyChecked 则父节点设置为PartiallyChecked
* 无 checked 则父节点设置为Unchecked
****************************************************/
if(NULL == item || NULL == item->parent()){
return;
}
// 统计父节点下选中状态
QStandardItem *parentItem = item->parent();
int allChildCount = parentItem->rowCount();
int checkedItemCount = 0; // 记录孩子有多少处于选中状态
bool hasPartly = false; // 记录孩子是否存在部分选中状态
for(int i=0; i<allChildCount; ++i){
QStandardItem *childItem = parentItem->child(i);
if(childItem->checkState() == Qt::Checked){
checkedItemCount++;
}
else if(childItem->checkState() == Qt::PartiallyChecked){
hasPartly = true;
break;
}
}
// 如果包含部分选中则直接设置为部分选中
if(hasPartly){
parentItem->setCheckState(Qt::PartiallyChecked);
}else{
// 一个也没选中
if(0 == checkedItemCount){
parentItem->setCheckState(Qt::Unchecked);
}
// 选中了一部分
else if(checkedItemCount > 0
&& checkedItemCount < allChildCount){
parentItem->setCheckState(Qt::PartiallyChecked);
}
// 一个也没选中
else if(checkedItemCount == allChildCount){
parentItem->setCheckState(Qt::Checked);
}
}
}
测试代码:
// 构建一个树模型
QStandardItemModel *model = new QStandardItemModel();
QStandardItem *root = model->invisibleRootItem();
QStandardItem *itemA = new QStandardItem("A");
QStandardItem *itemA1 = new QStandardItem("A1");
QStandardItem *itemA2 = new QStandardItem("A2");
QStandardItem *itemB = new QStandardItem("B");
QStandardItem *itemB1 = new QStandardItem("B1");
QStandardItem *itemB2 = new QStandardItem("B2");
QStandardItem *itemB21 = new QStandardItem("B21");
QStandardItem *itemB211 = new QStandardItem("B211");
QStandardItem *itemB212 = new QStandardItem("B212");
// 设置为可选
itemA->setCheckable(true);
itemA1->setCheckable(true);
itemA2->setCheckable(true);
itemB->setCheckable(true);
itemB1->setCheckable(true);
itemB2->setCheckable(true);
itemB21->setCheckable(true);
itemB211->setCheckable(true);
itemB212->setCheckable(true);
itemA->appendRow(itemA1);
itemA->appendRow(itemA2);
itemB21->appendRow(itemB211);
itemB21->appendRow(itemB212);
itemB2->appendRow(itemB21);
itemB->appendRow(itemB1);
itemB->appendRow(itemB2);
root->appendRow(itemA);
root->appendRow(itemB);
// treeView 被提升为自定义的CheckableTreeView
ui->treeView->setStandardItemModel(model);
效果: