這種三態(tài)叫Tristate。
要設(shè)置條目有復(fù)選框只需要使用QStandardItem的函數(shù)setCheckable,無論是兩態(tài)還是三態(tài)都需要先setCheckable,setCheckable默認(rèn)是兩態(tài),如果希望是三態(tài)的話,需要再setTristate
示例代碼如下:(樹形視圖節(jié)點(diǎn)的具體添加方法見上篇文章)
- QStandardItemModel* model = new QStandardItemModel(ui->treeView);
- model->setHorizontalHeaderLabels(QStringList()<<><>< li=""><><>
- QStandardItem* itemProject = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_Project")],QStringLiteral("項(xiàng)目"));
- model->appendRow(itemProject);
- model->setItem(model->indexFromItem(itemProject).row(),1,new QStandardItem(QStringLiteral("項(xiàng)目信息說明")));
- QStandardItem* itemFolder = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")],QStringLiteral("文件夾1"));
- itemProject->appendRow(itemFolder);
- itemProject->setChild(itemFolder->index().row(),1,new QStandardItem(QStringLiteral("信息說明")));
- itemFolder = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")],QStringLiteral("文件夾2"));
- itemProject->appendRow(itemFolder);
- for(int i=0;i<5;++i){
- QStandardItem* itemgroup = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_group")],QStringLiteral("組%1").arg(i+1));
- itemFolder->appendRow(itemgroup);
- for(int j=0;j<(i+1);++j){
- QStandardItem* itemchannel = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_channel")],QStringLiteral("頻道%1").arg(j+1));
- itemgroup->appendRow(itemchannel);
- itemgroup->setChild(itemchannel->index().row(),1,new QStandardItem(QStringLiteral("頻道%1信息說明").arg(j+1)));
- }
- }
- itemProject->setChild(itemFolder->index().row(),1,new QStandardItem(QStringLiteral("文件夾2信息說明")));
- itemProject = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_Project")],QStringLiteral("項(xiàng)目2"));
- model->appendRow(itemProject);
- for(int i =0;i<3;++i)
- {
- itemFolder = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")],QStringLiteral("項(xiàng)目2文件夾%1").arg(i+1));
- itemFolder->setCheckable(true);
- itemFolder->setTristate(true);
- QStandardItem* itemFolderDes = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_group")],QStringLiteral("文件夾%1組").arg(i+1));
- itemProject->appendRow(itemFolder);
- itemProject->setChild(itemFolder->index().row(),1,itemFolderDes);
- for(int j=0;j<>< li=""><>
- {
- QStandardItem* item = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_dataItem")],QStringLiteral("項(xiàng)目%1").arg(j+1));
- item->setCheckable(true);
- itemFolder->appendRow(item);
-
- }
- }
- //關(guān)聯(lián)項(xiàng)目屬性改變的信號(hào)和槽
- connect(model,&QStandardItemModel::itemChanged,this,&Widget::treeItemChanged);
- //connect(model,SIGNAL(itemChanged(QStandardItem*)),this,SLOT(treeItemChanged(QStandardItem*)));
- ui->treeView->setModel(model);
代碼中m_publicIconMap是QMap對(duì)象,用于存放定義好的圖標(biāo),在樹形視圖節(jié)點(diǎn)添加之前進(jìn)行初始化,初始化代碼如下:
- m_publicIconMap[QStringLiteral("treeItem_Project")] = QIcon(QStringLiteral(":/treeItemIcon/res_treeItemIcon/Project.png"));
- m_publicIconMap[QStringLiteral("treeItem_folder")] = QIcon(QStringLiteral(":/treeItemIcon/res_treeItemIcon/folder.png"));
- m_publicIconMap[QStringLiteral("treeItem_folder-ansys")] = QIcon(QStringLiteral(":/treeItemIcon/res_treeItemIcon/folder-ansys.png"));
- m_publicIconMap[QStringLiteral("treeItem_group")] = QIcon(QStringLiteral(":/treeItemIcon/res_treeItemIcon/group.png"));
- m_publicIconMap[QStringLiteral("treeItem_channel")] = QIcon(QStringLiteral(":/treeItemIcon/res_treeItemIcon/channel.png"));
效果圖:
2.三態(tài)復(fù)選框的智能關(guān)聯(lián)
三態(tài)復(fù)選框的主要體現(xiàn)就在樹形控件里,如果子項(xiàng)目全選,父級(jí)需要全選,如果子項(xiàng)目部分選,父級(jí)就是不完全選
下圖是三態(tài)的正確表現(xiàn)方法
但QTreeView在QStandardItem設(shè)置復(fù)選框后,并不是按照規(guī)則的,這時(shí)需要進(jìn)行代碼設(shè)置
2.1 捕獲復(fù)選框改變的信號(hào)
要對(duì)復(fù)選框進(jìn)行操作,首先需要捕獲樹形視圖的復(fù)選框改變發(fā)出的信號(hào)
通過QStandardItemModel設(shè)置的項(xiàng)目,任何改變都會(huì)觸發(fā)void QStandardItemModel::itemChanged(QStandardItem
* item)信號(hào)
因此需要定義一個(gè)槽函數(shù)和這個(gè)信號(hào)關(guān)聯(lián)
- private slots :
- void treeItem_CheckChildChanged ( QStandardItem * item );
關(guān)聯(lián)代碼寫在model創(chuàng)建之后的地方:
- //關(guān)聯(lián)項(xiàng)目屬性改變的信號(hào)和槽
- connect ( model ,&QStandardItemModel::itemChanged , this ,&Widget::treeItemChanged );
- //connect(model,SIGNAL(itemChanged(QStandardItem*)),this,SLOT(treeItemChanged(QStandardItem*)));
這里使用最新的信號(hào)和槽的關(guān)聯(lián)方法,記得在pro文件中加入如下,使得支持C++11
CONFIG+=c++11
槽函數(shù)的寫法如下:
void Widget
:: treeItemChanged ( QStandardItem
* item )
{
}
下面開始實(shí)現(xiàn)三態(tài)的自動(dòng)關(guān)聯(lián)(父子節(jié)點(diǎn)checkbox自動(dòng)關(guān)聯(lián))
2.2 父子節(jié)點(diǎn)復(fù)選框自動(dòng)關(guān)聯(lián)實(shí)現(xiàn)
- void Widget : : treeItemChanged ( QStandardItem * item )
- {
- if ( item == nullptr )
- return ;
- if ( item - > isCheckable ())
- {
- //如果條目是存在復(fù)選框的,那么就進(jìn)行下面的操作
- Qt : : CheckState state = item - > checkState (); //獲取當(dāng)前的選擇狀態(tài)
- if ( item - > isTristate ())
- {
- //如果條目是三態(tài)的,說明可以對(duì)子目錄進(jìn)行全選和全不選的設(shè)置
- if ( state != Qt : : PartiallyChecked )
- {
- //當(dāng)前是選中狀態(tài),需要對(duì)其子項(xiàng)目進(jìn)行全選
- treeItem_checkAllChild ( item , state == Qt : : Checked ? true : false );
- }
- }
- else
- {
- //說明是兩態(tài)的,兩態(tài)會(huì)對(duì)父級(jí)的三態(tài)有影響
- //判斷兄弟節(jié)點(diǎn)的情況
- treeItem_CheckChildChanged ( item );
- }
- }
- }
首先要判斷條目的狀態(tài),如果條目是有復(fù)選框的話,那么就進(jìn)行操作。通過函數(shù)isCheckable()可以判斷條目是否有復(fù)選框
在確認(rèn)條目有復(fù)選框后,需要獲取當(dāng)前條目的選中狀態(tài),使用checkState ()函數(shù)可以判斷當(dāng)前條目的選中狀態(tài);
現(xiàn)在分兩種情況:
1.如果條目是三態(tài)的,說明要判斷它的子節(jié)點(diǎn)。條目選中時(shí),所有子節(jié)點(diǎn)都將選中,條目不選中時(shí),所有子節(jié)點(diǎn)都不選中
2.如果條目是兩態(tài)的,說明可能會(huì)影響它的三態(tài)的父節(jié)點(diǎn),當(dāng)兩態(tài)節(jié)點(diǎn)選中且其所有的兄弟節(jié)點(diǎn)都選中,三態(tài)父節(jié)點(diǎn)選中,若兩態(tài)子節(jié)點(diǎn)和其兄弟節(jié)點(diǎn)都沒選中,那么其三態(tài)父節(jié)點(diǎn)將不選中,若果兄弟節(jié)點(diǎn)有選中有不選中,三態(tài)父節(jié)點(diǎn)將是處于不完全選中狀態(tài)
2.2.1 子節(jié)點(diǎn)遞歸全選
treeItem_checkAllChild 函數(shù)是用于使子節(jié)點(diǎn)全選的函數(shù)。這個(gè)函數(shù)實(shí)現(xiàn)如下:
- ///
- /// \brief 遞歸設(shè)置所有的子項(xiàng)目為全選或全不選狀態(tài)
- /// \param item 當(dāng)前項(xiàng)目
- /// \param check true時(shí)為全選,false時(shí)全不選
- ///
- void Widget::treeItem_checkAllChild(QStandardItem * item, bool check)
- {
- if(item == nullptr)
- return;
- int rowCount = item->rowCount();
- for(int i=0;i<>< li=""><>
- {
- QStandardItem* childItems = item->child(i);
- treeItem_checkAllChild_recursion(childItems,check);
- }
- if(item->isCheckable())
- item->setCheckState(check ? Qt::Checked : Qt::Unchecked);
- }
- void Widget::treeItem_checkAllChild_recursion(QStandardItem * item,bool check)
- {
- if(item == nullptr)
- return;
- int rowCount = item->rowCount();
- for(int i=0;i<>< li=""><>
- {
- QStandardItem* childItems = item->child(i);
- treeItem_checkAllChild_recursion(childItems,check);
- }
- if(item->isCheckable())
- item->setCheckState(check ? Qt::Checked : Qt::Unchecked);
- }
通過這個(gè)功能實(shí)現(xiàn),可以看看如何對(duì)樹形節(jié)點(diǎn)的所有子節(jié)點(diǎn)進(jìn)行遍歷,一般樹形節(jié)點(diǎn)的遍歷是通過遞歸來實(shí)現(xiàn)的(遞歸的效率不是最高的,可以把遞歸拆解為循環(huán))。
QStandardItem的child方法可以獲取它的下級(jí)子節(jié)點(diǎn),在這個(gè)方法之前現(xiàn)需要查明有多少個(gè)子節(jié)點(diǎn),rowCount()方法是獲取樹形節(jié)點(diǎn)下一級(jí)的子節(jié)點(diǎn)個(gè)數(shù)(在樹形視圖中,每個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)算作這個(gè)節(jié)點(diǎn)的條目,第一個(gè)節(jié)點(diǎn)就是第一行,第二個(gè)就是第二行,以此類推,如果樹形視圖有多列的話,那么列也會(huì)起作用)。
treeItem_checkAllChild_recursion是個(gè)遞歸函數(shù),通過這個(gè)函數(shù)可以把樹形節(jié)點(diǎn)的所有子節(jié)點(diǎn)遍歷一遍。
通過上面的這個(gè)方法,即可實(shí)現(xiàn)第一種情況。
2.2.2 父節(jié)點(diǎn)遞歸處理
treeItem_CheckChildChanged函數(shù)是用于處理第二種情況的,此函數(shù)主要對(duì)父級(jí)節(jié)點(diǎn)有影響,函數(shù)實(shí)現(xiàn)如下:
- ///
- /// \brief 根據(jù)子節(jié)點(diǎn)的改變,更改父節(jié)點(diǎn)的選擇情況
- /// \param item
- ///
- void Widget::treeItem_CheckChildChanged(QStandardItem * item)
- {
- if(nullptr == item)
- return;
- Qt::CheckState siblingState = checkSibling(item);
- QStandardItem * parentItem = item->parent();
- if(nullptr == parentItem)
- return;
- if(Qt::PartiallyChecked == siblingState)
- {
- if(parentItem->isCheckable() && parentItem->isTristate())
- parentItem->setCheckState(Qt::PartiallyChecked);
- }
- else if(Qt::Checked == siblingState)
- {
- if(parentItem->isCheckable())
- parentItem->setCheckState(Qt::Checked);
- }
- else
- {
- if(parentItem->isCheckable())
- parentItem->setCheckState(Qt::Unchecked);
- }
- treeItem_CheckChildChanged(parentItem);
- }
此函數(shù)也是一個(gè)遞歸函數(shù),首先要判斷的是父級(jí)是否到達(dá)頂層,到達(dá)底層作為遞歸的結(jié)束,然后通過函數(shù)checkSibling判斷當(dāng)前的兄弟節(jié)點(diǎn)的具體情況,checkSibling方法的實(shí)現(xiàn)如下:
- ///
- /// \brief 測(cè)量兄弟節(jié)點(diǎn)的情況,如果都選中返回Qt::Checked,都不選中Qt::Unchecked,不完全選中返回Qt::PartiallyChecked
- /// \param item
- /// \return 如果都選中返回Qt::Checked,都不選中Qt::Unchecked,不完全選中返回Qt::PartiallyChecked
- ///
- Qt::CheckState Widget::checkSibling(QStandardItem * item)
- {
- //先通過父節(jié)點(diǎn)獲取兄弟節(jié)點(diǎn)
- QStandardItem * parent = item->parent();
- if(nullptr == parent)
- return item->checkState();
- int brotherCount = parent->rowCount();
- int checkedCount(0),unCheckedCount(0);
- Qt::CheckState state;
- for(int i=0;i<>< li=""><>
- {
- QStandardItem* siblingItem = parent->child(i);
- state = siblingItem->checkState();
- if(Qt::PartiallyChecked == state)
- return Qt::PartiallyChecked;
- else if(Qt::Unchecked == state)
- ++unCheckedCount;
- else
- ++checkedCount;
- if(checkedCount>0 && unCheckedCount>0)
- return Qt::PartiallyChecked;
- }
- if(unCheckedCount>0)
- return Qt::Unchecked;
- return Qt::Checked;
- }
checkSibling用于判斷兄弟節(jié)點(diǎn)的關(guān)系,兄弟節(jié)點(diǎn)之間無外乎三種關(guān)系:
1.全選
2.全不選
3.部分選中
獲取QStandardItem的兄弟節(jié)點(diǎn)有多種方法,這里是通過獲取它的父級(jí)在獲取父級(jí)的子節(jié)點(diǎn)來得到包括它自己的所有兄弟節(jié)點(diǎn),另外QStandardItem可以通過函數(shù)QModelIndex index() const;獲取Item對(duì)應(yīng)的QModelIndex,QModelIndex有QModelIndex QModelIndex::sibling(int row, int column) const方法獲取兄弟節(jié)點(diǎn)。
通過以上幾個(gè)函數(shù),即可實(shí)現(xiàn)QTreeView的復(fù)選框及自動(dòng)識(shí)別勾選的功能。
下面放出效果圖:
|