Display file system and check linkage - Qt

Posted by chetanmadaan on Thu, 09 Dec 2021 08:04:22 +0100

preface

In order to realize the function such as title, I really almost gave up. Fortunately, I didn't give up. Of course, I can't give up

design sketch

Let's take a look at the final rendering. The left side shows the file system structure of the whole D disk (only folders), and the right side shows the file structure details of the clicked items on the left (including folders and files). You can see that when I check the folder on the right, both the corresponding items on the left and the child files on the right are linked accordingly.

Ideas and codes

Like the file resource manager of Windows system, because of the complexity and large number of file structures involved, it is not appropriate to load all data like operating ordinary and simple data in the past. This time I just met the project I wrote, so I can only find a way to solve it. The difficulties in realizing this function are:

1> The amount of data is large, and the tree relationship is too large;

2> Click to check linkage, keep the left and right consistent, and the interface data changes uniformly;

3> Clicking too fast may lead to conflict and crash (this problem has a lot to do with my implementation method).

Final solution:

1> When the selected item is expanded, only two levels of files are displayed: child items and grandchildren. When the selected item is expanded, the child level is displayed first. In order to display whether there are folders under the child level and whether it can be expanded, expand two layers.

2> In order to keep the data consistent, I set the structure storing data as a member variable, and the data of each item stores the structure related to this item in the member variable; The linkage of this side is realized through iteration; And the consistency between the left and right is   According to the consistency of the data, refresh the check status of the item and the data storage of the item. (this is the general idea, and the detailed logic is more complex)

3> Because the data of each item is stored to point to the structure related to the item in the member variable, that is, to the structure of the item data and its family data. When clicking too fast, although the code is executed, the internal resources may still be occupied, so there may be a conflict of occupying resources, which belongs to "pulling one to start the whole body" (which may be related to the multi-core of the computer). So it collapsed. In order to prevent the operation from being too fast, I added a one second animation after each click.

Left unfold

The expansion on the left is relatively simple, that is, you need to judge whether to add display items, that is, judge whether the grandchildren of the expanded items have been added to the interface, return if they have, and add if they have not.

void FileSystemWidget::addChildrenFolderAfterLeftExpend(QTreeWidgetItem *pCurItem)
{
    disconnect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(addChildrenFolderAfterLeftExpend(QTreeWidgetItem*)));
    myWaitDlg.showGif();

    //Determine whether display items need to be added
    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)
             {//Determine whether it is a folder
                 int childCount=childItem->childCount();
                 if(childCount!=0)
                 {
                     flag=true;
                     break;
                 }else{
                     flag=false;
                     break;
                 }
             }
        }
    }
    //Add display item
    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*)));
    });

}
//Add subfolder item
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)
            {//folder
                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);
            }

        }

    }
}

Click and tick change on the left

Because the signal itemclicked (QTreeWidget item *, int) of QTreeWidget will be triggered whether it is simply clicked or selected, I wrote the response required for clicking and checking (checkstate change). The common point between the two is that the data of this item (selected folder) needs to be displayed on the right. The difference is that the checkstate value of the data needs to be changed when checked.

Note: after I change the value of data (i.e. the value of structure isChecked), the pointer address stored in the data of the child item cannot be converted to the correct structure. I don't know why, so I resave the pointer after the change.

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);
    //Refresh of right data
    if(var.canConvert<MyFileData*>())
    {
        ui->treeWidget_2->clear();

        MyFileData* indxpage=var.value<MyFileData*>();
        //Set selected status
        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);

            //Child change check status
            childrenCheckStateChange(pCurItem);
            //Parent change check status
            parentCheckStateChange(pCurItem);

        }

        //Refresh right data
        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*)));
    });
}
//Children vary with state
void FileSystemWidget::childrenCheckStateChange(QTreeWidgetItem *pItem)
{
    //Change child data
    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]);
    }
}

//The parent changes as the state changes
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)
        {//Judge whether the statistics have been 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
        {//Check whether the parent class is 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);
        }
    }
}

Right click

The expansion on the right is similar to that on the left, so I won't give an example.

Clicking on the right side is much more complicated than clicking on the left side, which requires various considerations. The specific ideas are explained in the code, so I won't write.

void FileSystemWidget::rightTreeItemClicked(QTreeWidgetItem *pCurItem)
{//Click on the right to determine whether the checkstate changes. Change: Change on the right, corresponding to change on the left; No change: no response
    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;
        }
        //Current level data change
        pIndxPage->isChecked=isChecked;
        pCurItem->setData(0,Qt::UserRole+1,QVariant::fromValue(pIndxPage));
        pCurItem->setText(0, pIndxPage->strFileName);
    }
    //Right side change
    childrenCheckStateChange(pCurItem);
    parentCheckStateChange(pCurItem);

    //Left change
    updateLeftTreeAfterRightCheckStateChanged();
    QTimer::singleShot(1000,[=](){
        myWaitDlg.closeGif();
        connect(ui->treeWidget_2, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(rightTreeItemClicked(QTreeWidgetItem*)));

    });

}
//The left side will refresh after the status of the right check box changes
void FileSystemWidget::updateLeftTreeAfterRightCheckStateChanged()
{
    QVariant var=m_pShowLeftItem->data(0,Qt::UserRole+1);

    if(var.canConvert<MyFileData*>())
    {
        MyFileData* data=var.value<MyFileData*>();

        //Click the child of the current item on the left to refresh the interface according to the data
        updateChildrenItemsByData(m_pShowLeftItem,data);

        //Calculate whether this item is required according to the sub level data
        bool curCheck=data->isChecked;

        if(curCheck)
        {//Check whether all children are checked
            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{//Check whether all children are checked (i.e. check whether there is false), so the state needs to be changed
            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]);
    }
}

Conclusion

That's probably what I can write. If there is a better way, please tell me, thank you very much!

Topics: Qt