QtreeView是ui中最常用的控件,Qt中QTreeWidget比QTreeView更簡單,但沒有QTreeView那么靈活(QTreeWidget封裝的和MFC的CTreeCtrl很類似,沒有mvc的特點)。
1. QStandardItemModel在QTreeView中的使用
使用QTreeView的對應模型是QStandardItemModel,這個是Qt對應ui界面最有用的模型,它可以用於樹形控件、列表控件、表格控件等等和條目有關的控件。QStandardItemModel用於列表和表格控件還是很好理解的,但是用於樹形控件就有點難以理解了,實際上,在樹形控件中,QStandardItemModel也挺簡單的。
首先要做的當然是新建一個model對象,可以使用成員變量或者局部變量。成員變量好處是,使用這個model時不用調用函數和進行類型轉換,但如果在model銷毀時沒有對成員變量進行操作就可能發生不可預料的錯誤。
下面演示局部變量的做法:
QStandardItemModel* model = new QStandardItemModel(ui->treeView_Pro);
QStandardItemModel的父級最好定義,因為這樣可以不用你自己銷毀,Qt的智能指針機制是非常方便的。在這里定義了一個它關聯的樹形控件作為它的父級。
注意:如果這個模型有許多控件公用,那么它的父級最好是這些控件的父級窗口,因為,Qt的父級機制是“老爹死兒子必須先死”,如果控件A和控件B都同時使用模型1,而建立模型1時定義了模型1的控件A為其父級,那么如果控件A銷毀時,模型1也會被一起同歸於盡,而這時控件B就會發生不可預料的錯誤了。
1.1 表頭添加
表頭添加使用setHorizontalHeaderLabels函數最為簡單
- model->setHorizontalHeaderLabels(QStringList()<<QStringLiteral("項目名")<<QStringLiteral("信息"));
上段代碼將是添加兩個表頭,一個為項目名一個為信息,效果如下圖:(已經ui->treeView_Pro->
setModel(
model);)

1.2 給樹形視圖添加條目
在模型添加好后,說說條目的添加。
QStandardItemModel有setItem函數,用於添加條目,由於這是一個樹形控件,傳統的樹形控件只有最左邊才能展開,除了左邊的內容,右邊的內容是沒有展開能力的。添加樹形控件的根條目可以使用appendRow函數,setItem也可以。
- QStandardItem* itemProject = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_Project")],QStringLiteral("項目"));
- model->appendRow(itemProject);
- //以下作用同appendRow
- //model->setItem(0,0,itemProject);
- //model->setItem(0,itemProject);
代碼中m_publicIconMap是定義好的圖標其在之前進行初始化,初始化代碼如下:
- 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"));
圖標:

上段代碼的運行效果如圖:

下面給這個項目條目下添加一個子項目。
子項目的添加需要操作QStandardItem,既是上面代碼創建的
itemProject變量。
QStandardItem的appendRow和setChild方法等價於QStandardItemModel的appendRow和setItem
- QStandardItem* itemChild = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")],QStringLiteral("文件夾1"));
- itemProject->appendRow(itemChild);
- //setChild效果同上
- //itemProject->setChild(0,itemChild);
上面代碼執行后給 itemProject條目添加了一個行,這一行屬於他的子條目,上代碼運行效果如下圖:

這樣就可以隨心所欲的添加了。但是第二列的信息怎么添加呢。
其實道理一樣,QStandardItemModel 的setItem和QStandardItem的setChild函數都有關於列的重載,具體看下面的代碼:
- QStandardItem* itemProject = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_Project")],QStringLiteral("項目"));
- model->appendRow(itemProject);
- model->setItem(0/*model->indexFromItem(itemProject).row()*/,1,new QStandardItem(QStringLiteral("項目信息說明")));
- QStandardItem* itemChild = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")],QStringLiteral("文件夾1"));
- itemProject->appendRow(itemChild);
- itemProject->setChild(0/*itemChild->index().row()*/,1,new QStandardItem(QStringLiteral("信息說明")));
效果:

使用model->indexFromItem(itemProject).row()可以不用記得當前的條目是第幾行。
對於復雜的目錄生成見下面這段代碼:
- QStandardItemModel* model = new QStandardItemModel(ui->treeView_Pro);
- model->setHorizontalHeaderLabels(QStringList()<<QStringLiteral("項目名")<<QStringLiteral("信息"));
- QStandardItem* itemProject = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_Project")],QStringLiteral("項目"));
- model->appendRow(itemProject);
- model->setItem(model->indexFromItem(itemProject).row(),1,new QStandardItem(QStringLiteral("項目信息說明")));
- 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 = newQStandardItem(m_publicIconMap[QStringLiteral("treeItem_group")],QStringLiteral("組%1").arg(i+1));
- itemFolder->appendRow(itemgroup);
- for(int j=0;j<(i+1);++j){
- QStandardItem* itemchannel = newQStandardItem(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信息說明")));
- ui->treeView_Pro->setModel(model);
效果:

1.3 條目的其他操作
1.3.1 獲取當前選中的條目
通過QTreeView函數currentIndex()可以獲取當前選中條目的QModelIndex,QModelIndex可以看做是QStandardItem的數據封裝,知道QModelIndex就可以知道QStandardItem,通過QStandardItemModel的itemFromIndex函數即可得到QModelIndex對應的QStandardItem。
如:
QStandardItemModel* model = static_cast<QStandardItemModel*>(ui->treeView->model());
QModelIndex currentIndex = ui->treeView->currentIndex();
QStandardItem* currentItem = model->itemFromIndex(currentIndex);
這里編一個小程序獲取當前選中的樹形條目
代碼如下:
- void Widget::on_treeView_clicked(const QModelIndex &index)
- {
- QString str;
- str += QStringLiteral("當前選中:%1\nrow:%2,column:%3\n").arg(index.data().toString())
- .arg(index.row()).arg(index.column());
- str += QStringLiteral("父級:%1\n").arg(index.parent().data().toString());
- ui->label_realTime->setText(str);
- }
on_treeView_clicked(const QModelIndex & index)是樹形控件項目點擊的槽響應函數

程序運行結果如下:當點擊頻道1時,顯示頻道1,
當點擊旁邊的信息說明時選中的是頻道1旁邊的信息說明條目

有時候,“頻道1”和“頻道1信息說明”是屬於同一個條目,再選擇“頻道1信息說明”時,我們可能想得到的是旁邊位於最左邊的“頻道1”,於是就涉及到兄弟節點的獲取。
1.3.2 兄弟節點獲取
節點間無父子關系,有並列關系的就稱為兄弟節點,如下圖紅框內的10個節點都屬於兄弟節點。

最常用的兄弟節點獲取是“左右”節點,例如點擊“頻道1”要知道頻道1的信息,就需要獲取“頻道1”右邊的兄弟節點“頻道1信息說明”
QModelIndex QAbstractItemModel::sibling(int row, int column, const QModelIndex & index)
和
QModelIndex QModelIndex::sibling(int row, int column) const
都可以用於獲取兄弟節點信息
例如把on_treeView_clicked(const QModelIndex &
index)的代碼改一下,每點擊一條目,無論點擊哪里,都能獲取它的“名稱”和“信息”:
- void Widget::on_treeView_clicked(const QModelIndex &index)
- {
- QString str;
- str += QStringLiteral("當前選中:%1\nrow:%2,column:%3\n").arg(index.data().toString())
- .arg(index.row()).arg(index.column());
- str += QStringLiteral("父級:%1\n").arg(index.parent().data().toString());
- QString name,info;
- if(index.column() == 0)
- {
- name = index.data().toString();
- info = index.sibling(index.row(),1).data().toString();
- }
- else
- {
- name = index.sibling(index.row(),0).data().toString();
- info = index.data().toString();
- }
- str += QStringLiteral("名稱:%1\n信息:%2").arg(name).arg(info);
- ui->label_realTime->setText(str);
- }

QStandardItem * QStandardItemModel::invisibleRootItem()函數並不是得到我們想要的這個頂層節點,它得到的是所有節點的最終根節點,因此,得到頂層節點需要自己寫操作,下面是根據任意一個節點獲取其可見頂層節點的代碼:
- QStandardItem* getTopParent(QStandardItem* item)
- {
- QStandardItem* secondItem = item;
- while(item->parent()!= 0)
- {
- secondItem = item->parent();
- item = secondItem;
- }
- if(secondItem->index().column() != 0)
- {
- QStandardItemModel* model = static_cast<QStandardItemModel*>(ui->treeView->model());
- secondItem = model->itemFromIndex(secondItem->index().sibling(secondItem->index().row(),0));
- }
- return secondItem;
- }
- QModelIndex getTopParent(QModelIndex itemIndex)
- {
- QModelIndex secondItem = itemIndex;
- while(itemIndex.parent().isValid())
- {
- secondItem = itemIndex.parent();
- itemIndex = secondItem;
- }
- if(secondItem.column() != 0)
- {
- secondItem = secondItem.sibling(secondItem.row(),0);
- }
- return secondItem;
- }
根據任意節點信息找到其最后的父級節點
使用如下:
QString top = getTopParent(index).data().toString();
str += QStringLiteral("頂層節點名:%1\n").arg(top);
效果:

demo代碼下載地址:
http://download.csdn.net/detail/czyt1988/7293383
通過QStandardItem和QStandardItemModel可以很簡單方便的給QTreeView添加節點,但是,許多樹形控件都需要樹的節點需要一個復選框(checkBox),網上許多資料都是通過自定義model來實現的,而且不能很好的實現checkbox的父子關聯(父節點選中子節點全部選中,父節點不選,子節點全部選),下面將介紹如何使用QStandardItem和QStandardItemModel實現復選框,且實現父子關聯
1.使用QStandardItem使樹形控件條目帶上復選框
復選框在樹形控件中經常見到,在QStandardItem中已經封裝好了對復選框的一些設置
- void QStandardItem:: setCheckable ( bool checkable )
- void QStandardItem:: setTristate ( bool tristate )
- void QStandardItem:: setCheckState ( Qt::CheckState state )
- Qt::CheckState QStandardItem:: checkState () const
- bool QStandardItem:: isCheckable () const
- bool QStandardItem:: isTristate () const
從字面意思就知道這些函數是干什么的了,但這里要注意一些,checkBox有兩種情況,
一種是兩態,就是選中和不選中
一種是三態,選中、不選中、不完全選中,如圖:

這種三態叫Tristate。
要設置條目有復選框只需要使用QStandardItem的函數setCheckable,無論是兩態還是三態都需要先setCheckable,setCheckable默認是兩態,如果希望是三態的話,需要再setTristate
示例代碼如下:(樹形視圖節點的具體添加方法見上篇文章)
- QStandardItemModel* model = new QStandardItemModel(ui->treeView);
- model->setHorizontalHeaderLabels(QStringList()<<QStringLiteral("項目名")<<QStringLiteral("信息"));
- QStandardItem* itemProject = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_Project")],QStringLiteral("項目"));
- model->appendRow(itemProject);
- model->setItem(model->indexFromItem(itemProject).row(),1,new QStandardItem(QStringLiteral("項目信息說明")));
- 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("項目2"));
- model->appendRow(itemProject);
- for(int i =0;i<3;++i)
- {
- itemFolder = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_folder")],QStringLiteral("項目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<i+1;++j)
- {
- QStandardItem* item = new QStandardItem(m_publicIconMap[QStringLiteral("treeItem_dataItem")],QStringLiteral("項目%1").arg(j+1));
- item->setCheckable(true);
- itemFolder->appendRow(item);
- }
- }
- //關聯項目屬性改變的信號和槽
- connect(model,&QStandardItemModel::itemChanged,this,&Widget::treeItemChanged);
- //connect(model,SIGNAL(itemChanged(QStandardItem*)),this,SLOT(treeItemChanged(QStandardItem*)));
- ui->treeView->setModel(model);
代碼中m_publicIconMap是QMap<QString,QIcon>對象,用於存放定義好的圖標,在樹形視圖節點添加之前進行初始化,初始化代碼如下:
- 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"));
效果圖:

但QTreeView在QStandardItem設置復選框后,並不是按照規則的,這時需要進行代碼設置
2.1 捕獲復選框改變的信號
要對復選框進行操作,首先需要捕獲樹形視圖的復選框改變發出的信號
通過QStandardItemModel設置的項目,任何改變都會觸發void QStandardItemModel::itemChanged(QStandardItem * item)信號
因此需要定義一個槽函數和這個信號關聯
- private slots :
- void treeItem_CheckChildChanged ( QStandardItem * item );
關聯代碼寫在model創建之后的地方:
- //關聯項目屬性改變的信號和槽
- connect ( model ,&QStandardItemModel::itemChanged , this ,&Widget::treeItemChanged );
- //connect(model,SIGNAL(itemChanged(QStandardItem*)),this,SLOT(treeItemChanged(QStandardItem*)));
這里使用最新的信號和槽的關聯方法,記得在pro文件中加入如下,使得支持C++11
CONFIG+=c++11
槽函數的寫法如下:
void Widget :: treeItemChanged ( QStandardItem * item )
{
}
{
}
下面開始實現三態的自動關聯(父子節點checkbox自動關聯)
2.2 父子節點復選框自動關聯實現
- void Widget : : treeItemChanged ( QStandardItem * item )
- {
- if ( item == nullptr )
- return ;
- if ( item - > isCheckable ())
- {
- //如果條目是存在復選框的,那么就進行下面的操作
- Qt : : CheckState state = item - > checkState (); //獲取當前的選擇狀態
- if ( item - > isTristate ())
- {
- //如果條目是三態的,說明可以對子目錄進行全選和全不選的設置
- if ( state != Qt : : PartiallyChecked )
- {
- //當前是選中狀態,需要對其子項目進行全選
- treeItem_checkAllChild ( item , state == Qt : : Checked ? true : false );
- }
- }
- else
- {
- //說明是兩態的,兩態會對父級的三態有影響
- //判斷兄弟節點的情況
- treeItem_CheckChildChanged ( item );
- }
- }
- }
首先要判斷條目的狀態,如果條目是有復選框的話,那么就進行操作。通過函數isCheckable()可以判斷條目是否有復選框
在確認條目有復選框后,需要獲取當前條目的選中狀態,使用checkState ()函數可以判斷當前條目的選中狀態;
現在分兩種情況:
1.如果條目是三態的,說明要判斷它的子節點。條目選中時,所有子節點都將選中,條目不選中時,所有子節點都不選中
2.如果條目是兩態的,說明可能會影響它的三態的父節點,當兩態節點選中且其所有的兄弟節點都選中,三態父節點選中,若兩態子節點和其兄弟節點都沒選中,那么其三態父節點將不選中,若果兄弟節點有選中有不選中,三態父節點將是處於不完全選中狀態
2.2.1 子節點遞歸全選
treeItem_checkAllChild 函數是用於使子節點全選的函數。這個函數實現如下:
- ///
- /// \brief 遞歸設置所有的子項目為全選或全不選狀態
- /// \param item 當前項目
- /// \param check true時為全選,false時全不選
- ///
- void Widget::treeItem_checkAllChild(QStandardItem * item, bool check)
- {
- if(item == nullptr)
- return;
- int rowCount = item->rowCount();
- for(int i=0;i<rowCount;++i)
- {
- 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<rowCount;++i)
- {
- QStandardItem* childItems = item->child(i);
- treeItem_checkAllChild_recursion(childItems,check);
- }
- if(item->isCheckable())
- item->setCheckState(check ? Qt::Checked : Qt::Unchecked);
- }
通過這個功能實現,可以看看如何對樹形節點的所有子節點進行遍歷,一般樹形節點的遍歷是通過遞歸來實現的(遞歸的效率不是最高的,可以把遞歸拆解為循環)。
QStandardItem的child方法可以獲取它的下級子節點,在這個方法之前現需要查明有多少個子節點,rowCount()方法是獲取樹形節點下一級的子節點個數(在樹形視圖中,每個節點的子節點算作這個節點的條目,第一個節點就是第一行,第二個就是第二行,以此類推,如果樹形視圖有多列的話,那么列也會起作用)。
treeItem_checkAllChild_recursion是個遞歸函數,通過這個函數可以把樹形節點的所有子節點遍歷一遍。
通過上面的這個方法,即可實現第一種情況。
2.2.2 父節點遞歸處理
treeItem_CheckChildChanged函數是用於處理第二種情況的,此函數主要對父級節點有影響,函數實現如下:
- ///
- /// \brief 根據子節點的改變,更改父節點的選擇情況
- /// \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);
- }
此函數也是一個遞歸函數,首先要判斷的是父級是否到達頂層,到達底層作為遞歸的結束,然后通過函數checkSibling判斷當前的兄弟節點的具體情況,checkSibling方法的實現如下:
- ///
- /// \brief 測量兄弟節點的情況,如果都選中返回Qt::Checked,都不選中Qt::Unchecked,不完全選中返回Qt::PartiallyChecked
- /// \param item
- /// \return 如果都選中返回Qt::Checked,都不選中Qt::Unchecked,不完全選中返回Qt::PartiallyChecked
- ///
- Qt::CheckState Widget::checkSibling(QStandardItem * item)
- {
- //先通過父節點獲取兄弟節點
- 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<brotherCount;++i)
- {
- 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用於判斷兄弟節點的關系,兄弟節點之間無外乎三種關系:
1.全選
2.全不選
3.部分選中
獲取QStandardItem的兄弟節點有多種方法,這里是通過獲取它的父級在獲取父級的子節點來得到包括它自己的所有兄弟節點,另外QStandardItem可以通過函數QModelIndex index() const;獲取Item對應的QModelIndex,QModelIndex有QModelIndex QModelIndex::sibling(int row, int column) const方法獲取兄弟節點。
通過以上幾個函數,即可實現QTreeView的復選框及自動識別勾選的功能。
下面放出效果圖:

轉載網址:http://blog.csdn.net/czyt1988/article/details/18996407