标签:Qt pCurItem 文件系统 QTreeWidgetItem 勾选 isChecked var data
前言
为了实现如标题的功能,我真的是差点放弃了,还好我没放弃,当然,我也没办法放弃……
效果图
先看一下最终的效果图,左侧显示了整个D盘的文件系统结构(只包含文件夹),右侧显示左侧点击项的文件结构详情(包括文件夹及文件)。可以看到当我勾选右侧的文件夹,无论左侧对应项,还是右侧的子级文件都进行了相应的联动。
思路及代码
像Windows系统的文件资源管理器,因为涉及的文件结构的复杂和数量之大,像以前操作普通的、简单数据那样,将所有数据加载根本不合适。而这次我写的项目就正好碰到了,只能想办法解决。而要实现此功能难点有:
1>数据量大,树形关系过于庞大;
2>点击勾选联动,并且保持左右一致,并且界面数据统一变化;
3>点击过快可能导致冲突,崩溃(此问题跟我的实现方法有很大的关系)。
最终的解决办法:
1>选中项展开时,只显示两层文件:子级项,及孙级项。因为展开选中项时,首先显示的是子级,为了显示子级下是否还有文件夹,能否展开,所以展开两层。
2>为了保持数据的一致,我将存储数据的结构体设置为成员变量,而每一项的数据(data)存储着指向成员变量中与本项相关的结构体;本侧的联动,通过迭代实现;而左右的一致,则是 根据数据的一致性,重新刷新项的勾选状态和项的数据存储。(大概思路这样,详细逻辑更为复杂)
3>由于我每项的数据存储着指向成员变量中与本项相关的结构体,即指向本项数据及其家族数据的结构体,而点击过快时,虽然代码执行完了,但是内部资源可能还在占用,所以可能会出现占用资源的冲突,属于“牵一发动全身”(可能跟电脑的多核有关)。所以就崩了,为了防止操作过快,所以我每次点击后加了一个一秒的动画。
左侧展开
左侧展开比较简单,就是需要判断一下是否需要添加显示项,即判断展开项的孙级是否有添加到界面上,有则返回,没有则添加。
void FileSystemWidget::addChildrenFolderAfterLeftExpend(QTreeWidgetItem *pCurItem)
{
disconnect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(addChildrenFolderAfterLeftExpend(QTreeWidgetItem*)));
myWaitDlg.showGif();
//判断是否需要添加显示项
bool flag=true;
for(int i=0;i<pCurItem->childCount();i++)
{
QTreeWidgetItem* childItem=pCurItem->child(i);
QVariant var = childItem->data(0, Qt::UserRole + 1);
if(var.canConvert<MyFileData*>())
{
MyFileData* rootDir=var.value<MyFileData*>();
if (rootDir->fileordir==SYS_DIRECTORY)
{//判断是否是文件夹
int childCount=childItem->childCount();
if(childCount!=0)
{
flag=true;
break;
}else{
flag=false;
break;
}
}
}
}
//添加显示项
if(!flag)
{
for(int i=0;i<pCurItem->childCount();i++)
{
QTreeWidgetItem* childItem=pCurItem->child(i);
bool ischecked;
QVariant var = childItem->data(0, Qt::UserRole + 1);
if (var.canConvert<MyFileData*>())
{
MyFileData* page = var.value<MyFileData*>();
ischecked = page->isChecked;
childItem->setData(0, Qt::UserRole + 1, QVariant::fromValue(page));
}
childItem->setCheckState(0,ischecked?Qt::Checked:Qt::Unchecked);
addChildrenFolderOfItem(childItem);
}
}
QTimer::singleShot(1000,[=](){
myWaitDlg.closeGif();
connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(addChildrenFolderAfterLeftExpend(QTreeWidgetItem*)));
});
}
//添加子文件夹项
void FileSystemWidget::addChildrenFolderOfItem(QTreeWidgetItem *pCurItem)
{
QVariant var=pCurItem->data(0,Qt::UserRole+1);
if(var.canConvert<MyFileData*>())
{
MyFileData* indxpage=var.value<MyFileData*>();
for (int i = 0; i<indxpage->stdChildIndx.count(); i++)
{
MyFileData childpage=indxpage->stdChildIndx[i];
if (childpage.fileordir==SYS_DIRECTORY)
{//文件夹
QTreeWidgetItem *pChildItem=new QTreeWidgetItem;
pChildItem->setCheckState(0, childpage.isChecked ? Qt::Checked : Qt::Unchecked);
pChildItem->setText(0, childpage.strFileName);
pChildItem->setIcon(0,QIcon(":/res/folder.png"));
pChildItem->setData(0,Qt::UserRole+1,QVariant::fromValue(&indxpage->stdChildIndx[i]));
pCurItem->addChild(pChildItem);
}
}
}
}
左侧点击及勾选变化
因为QTreeWidget的信号 itemClicked(QTreeWidgetItem*, int)无论是单纯的点击还是选中,都会被触发,所以我将点击和勾选(checkstate变化)需要的响应写一起了。两者的共同点是都需要将本项(选中文件夹)的数据显示到右侧。不同点是勾选时需要改变数据的checkstate值。
注意:在我改变了data的值(即结构体isChecked的值)后,子级项的data存的指针地址不能转为正确的结构体,不知道为啥,所以在改变后,我重新保存的指针。
void FileSystemWidget::leftTreeItemClicked(QTreeWidgetItem *pCurItem)
{
disconnect(ui->treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(leftTreeItemClicked(QTreeWidgetItem*)));
if(pCurItem==nullptr)
return;
myWaitDlg.showGif();
m_pShowLeftItem=pCurItem;
QVariant var=pCurItem->data(0,Qt::UserRole+1);
//右侧数据的刷新
if(var.canConvert<MyFileData*>())
{
ui->treeWidget_2->clear();
MyFileData* indxpage=var.value<MyFileData*>();
//设置选中状态
bool curIsChecked=pCurItem->checkState(0)==Qt::Unchecked?false:true;
if(indxpage->isChecked!=curIsChecked)
{
indxpage->isChecked=curIsChecked;
pCurItem->setData(0,Qt::UserRole+1,QVariant::fromValue(indxpage));
pCurItem->setText(0, indxpage->strFileName);
//子级改变勾选状态
childrenCheckStateChange(pCurItem);
//父级改变勾选状态
parentCheckStateChange(pCurItem);
}
//刷新右侧数据
for (int i=0; i< indxpage->stdChildIndx.count(); i++)
{
MyFileData childpage=indxpage->stdChildIndx[i];
QTreeWidgetItem *pChildItem=new QTreeWidgetItem;
pChildItem->setCheckState(0,childpage.isChecked?Qt::Checked:Qt::Unchecked);
pChildItem->setData(0,Qt::UserRole+1,QVariant::fromValue(&indxpage->stdChildIndx[i]));
pChildItem->setText(0, childpage.strFileName);
...
...
...
ui->treeWidget_2->addTopLevelItem(pChildItem);
addChildrenOfItem(pChildItem);
}
}
QTimer::singleShot(1000,[=](){
myWaitDlg.closeGif();
connect(ui->treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(leftTreeItemClicked(QTreeWidgetItem*)));
});
}
//子级随状态变化而变化
void FileSystemWidget::childrenCheckStateChange(QTreeWidgetItem *pItem)
{
//改变子级数据
QVariant var=pItem->data(0,Qt::UserRole+1);
if(var.canConvert<MyFileData*>())
{
MyFileData* page = var.value<MyFileData*>();
changeDataCheckValOfChildren(page);
pItem->setData(0, Qt::UserRole + 1, QVariant::fromValue(page));
updateChildrenItemsByData(pItem,page);
}
}
void FileSystemWidget::changeDataCheckValOfChildren(MyFileData *prop)
{
bool isCheck=prop->isChecked;
for(int i=0;i<prop->stdChildIndx.count();i++)
{
prop->stdChildIndx[i].isChecked=isCheck;
changeDataCheckValOfChildren(&prop->stdChildIndx[i]);
}
}
//父级随状态变化而变化
void FileSystemWidget::parentCheckStateChange(QTreeWidgetItem *pItem)
{
Qt::CheckState checkState=pItem->checkState(0);
bool isCheck=checkState==Qt::Checked?true:false;
QTreeWidgetItem* pParentItem=pItem->parent();
if(pParentItem!=nullptr)
{
if(isCheck)
{//判断统计是否都已checked
bool isAllCheck=true;
for(int i=0;i<pParentItem->childCount();i++)
{
QTreeWidgetItem* pTmpItem=pParentItem->child(i);
Qt::CheckState tmpState=pTmpItem->checkState(0);
if(tmpState!=Qt::Checked)
{
isAllCheck=false;
break;
}
}
if(isAllCheck)
{
pParentItem->setCheckState(0,Qt::Checked);
QVariant var=pParentItem->data(0,Qt::UserRole+1);
if(var.canConvert<MyFileData*>())
{
MyFileData* page=var.value<MyFileData*>();
page->isChecked=true;
pParentItem->setData(0,Qt::UserRole+1,QVariant::fromValue(page));
pParentItem->setText(0, page->strFileName);
parentCheckStateChange(pParentItem);
}
}
}else
{//检查父类是否Checked
if(pParentItem->checkState(0)==Qt::Checked)
{
pParentItem->setCheckState(0,Qt::Unchecked);
QVariant var=pParentItem->data(0,Qt::UserRole+1);
if(var.canConvert<MyFileData*>())
{
MyFileData* page=var.value<MyFileData*>();
page->isChecked=false;
pParentItem->setData(0,Qt::UserRole+1,QVariant::fromValue(page));
pParentItem->setText(0, page->strFileName);
parentCheckStateChange(pParentItem);
}
}
}
}
}
void FileSystemWidget::addChildrenOfItem(QTreeWidgetItem *pCurItem)
{
QVariant var=pCurItem->data(0,Qt::UserRole+1);
if(var.canConvert<MyFileData*>())
{
MyFileData* indxpage=var.value<MyFileData*>();
bool curIsCheck=indxpage->isChecked;
for (int i=0; i<indxpage->stdChildIndx.count(); i++)
{
MyFileData childpage=indxpage->stdChildIndx[i];
QTreeWidgetItem *pChildItem=new QTreeWidgetItem;
indxpage->stdChildIndx[i].isChecked=curIsCheck;
pChildItem->setCheckState(0,curIsCheck?Qt::Checked:Qt::Unchecked);
pChildItem->setData(0,Qt::UserRole+1,QVariant::fromValue(&indxpage->stdChildIndx[i]));
pChildItem->setText(0, childpage.strFileName);
...
...
...
pCurItem->addChild(pChildItem);
}
}
}
右侧点击
右侧展开跟左侧展开差不多,就不再举例说明了。
右侧点击比左侧点击要复杂的多,要各种考虑。具体思路在代码中都有说明,我就不写字了。
void FileSystemWidget::rightTreeItemClicked(QTreeWidgetItem *pCurItem)
{//右侧点击,判断checkstate是否变化,变化:右侧变化,左侧对应变化;不变化:无反应
disconnect(ui->treeWidget_2, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(rightTreeItemClicked(QTreeWidgetItem*)));
myWaitDlg.showGif();
QVariant var=pCurItem->data(0,Qt::UserRole+1);
bool isChecked=pCurItem->checkState(0)==Qt::Checked?true:false;
f(var.canConvert<MyFileData*>())
{
MyFileData* pIndxPage=var.value<MyFileData*>();
bool dataCheck=pIndxPage->isChecked;
if(dataCheck==isChecked)
{
QTimer::singleShot(1000,[=](){
myWaitDlg.closeGif();
connect(ui->treeWidget_2, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(rightTreeItemClicked(QTreeWidgetItem*)));
});
return;
}
//本级数据变化
pIndxPage->isChecked=isChecked;
pCurItem->setData(0,Qt::UserRole+1,QVariant::fromValue(pIndxPage));
pCurItem->setText(0, pIndxPage->strFileName);
}
//右侧变化
childrenCheckStateChange(pCurItem);
parentCheckStateChange(pCurItem);
//左侧变化
updateLeftTreeAfterRightCheckStateChanged();
QTimer::singleShot(1000,[=](){
myWaitDlg.closeGif();
connect(ui->treeWidget_2, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(rightTreeItemClicked(QTreeWidgetItem*)));
});
}
//右侧勾选状态改变后,左侧刷新
void FileSystemWidget::updateLeftTreeAfterRightCheckStateChanged()
{
QVariant var=m_pShowLeftItem->data(0,Qt::UserRole+1);
if(var.canConvert<MyFileData*>())
{
MyFileData* data=var.value<MyFileData*>();
//左侧点击的当前项的子级根据数据刷新界面
updateChildrenItemsByData(m_pShowLeftItem,data);
//根据子级数据推算出本项需不需要
bool curCheck=data->isChecked;
if(curCheck)
{//检查子级是否都勾选
bool isChanged=false;
for(int i=0;i<data->stdChildIndx.count();i++)
{
if(!data->stdChildIndx[i].isChecked)
{
isChanged=true;
break;
}
}
if(isChanged)
{
data->isChecked=false;
m_pShowLeftItem->setData(0,Qt::UserRole+1,QVariant::fromValue(data));
m_pShowLeftItem->setText(0, data->strFileName);
m_pShowLeftItem->setCheckState(0,Qt::Unchecked);
parentCheckStateChange(m_pShowLeftItem);
}
}else{//检查子级是否全部被勾选(即检查是否有false),从而需要改变状态
bool isHaveFalse=false;
for(int i=0;i<data->stdChildIndx.count();i++)
{
if(!data->stdChildIndx[i].isChecked)
{
isHaveFalse=true;
break;
}
}
if(!isHaveFalse&&data->stdChildIndx.count()!=0)
{
data->isChecked=true;
m_pShowLeftItem->setData(0,Qt::UserRole+1,QVariant::fromValue(data));
m_pShowLeftItem->setText(0, data->strFileName);
m_pShowLeftItem->setCheckState(0,Qt::Checked);
parentCheckStateChange(m_pShowLeftItem);
}
}
}
}
void FileSystemWidget::updateChildrenItemsByData(QTreeWidgetItem* pItem,MyFileData *prop)
{
for(int i=0;i<pItem->childCount();i++)
{
QTreeWidgetItem* pChildItem=pItem->child(i);
bool isCheck=prop->stdChildIndx[i].isChecked;
pChildItem->setCheckState(0,isCheck?Qt::Checked:Qt::Unchecked);
pChildItem->setData(0,Qt::UserRole+1,QVariant::fromValue(&prop->stdChildIndx[i]));
updateChildrenItemsByData(pChildItem,&prop->stdChildIndx[i]);
}
}
结束语
我能写出来的大概也就这样了。如果有更好的方法,请告诉我,不胜感激!
标签:Qt,pCurItem,文件系统,QTreeWidgetItem,勾选,isChecked,var,data 来源: https://blog.csdn.net/xiaopei_yan/article/details/121638040
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。