Qt 是一個跨平臺C++圖形介面開發庫,利用Qt可以快速開發跨平臺表單應用程式,在Qt中我們可以通過拖拽的方式將不同元件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹TreeWidget
樹形選擇元件的常用方法及靈活運用。
QTreeWidget
是 Qt 中的樹形控制元件元件,用於顯示樹形結構的資料。它繼承自 QTreeView
和 QTreeWidget
,提供了一個方便的方式來展示和編輯包含層次結構資料的專案。
以下是 QTreeWidget
類的一些常用方法,說明和概述:
方法 | 描述 |
---|---|
addTopLevelItem(QTreeWidgetItem *item) |
向樹中新增一個頂級專案。 |
addTopLevelItems(const QList<QTreeWidgetItem *> &items) |
向樹中新增多個頂級專案。 |
clear() |
清除樹中的所有專案。 |
currentItem() |
返回當前選擇的專案。 |
currentIndex() |
返回當前選擇的專案的模型索引。 |
editItem(QTreeWidgetItem *item, int column) |
進入編輯模式以編輯給定專案的指定列。 |
headerItem() |
返回樹的標題專案,該專案可用於設定標題標籤。 |
invisibleRootItem() |
返回樹的不可見根專案。 |
itemAbove(QTreeWidgetItem *item) |
返回給定專案的上面一個專案。 |
itemBelow(QTreeWidgetItem *item) |
返回給定專案的下面一個專案。 |
setCurrentItem(QTreeWidgetItem *item) |
設定當前選擇的專案。 |
topLevelItem(int index) |
返回樹中給定索引的頂級專案。 |
topLevelItemCount() |
返回樹的頂級專案的數量。 |
insertTopLevelItem(int index, QTreeWidgetItem *item) |
在給定索引處插入一個頂級專案。 |
insertTopLevelItems(int index, const QList<QTreeWidgetItem *> &items) |
在給定索引處插入多個頂級專案。 |
takeTopLevelItem(int index) |
從樹中移除給定索引處的頂級專案,並返回該專案的指標。 |
scrollToItem(QTreeWidgetItem *item, QAbstractItemView::ScrollHint hint = EnsureVisible) |
捲動樹以確保給定專案可見。 |
sortItems(int column, Qt::SortOrder order = Qt::AscendingOrder) |
對樹中的專案進行排序。 |
findItems(const QString &text, Qt::MatchFlags flags, int column = 0) |
查詢樹中包含指定文字的專案。 |
這只是 QTreeWidget
類的一小部分方法。你可以查閱官方檔案以獲取完整的方法列表,以及這些方法的詳細說明。
首先我們來繪製一下UI
介面,由於該節點同時具備編輯功能所以實現起來要稍微複雜一些,我們分別在最左側放置一個TreeWidget
元件,在中間放置不同的PushButton
元件,最後是一個plainTextEdit
元件用來接收反饋,如下圖所示;
如下程式碼是在 Qt 中使用 QTreeWidget
初始化一個樹形結構,其中包含了朋友、同學和陌生人等不同分類的節點。
以下是概述:
QTreeWidget
: 設定 QTreeWidget
的一些基本屬性,包括列數、標題的隱藏等。QTreeWidgetItem
建立一個朋友節點,並設定圖示、選擇狀態等屬性。然後新增兩個子節點 "老張" 和 "老王",分別設定圖示和選擇狀態。QTreeWidgetItem
直接建立一個陌生人節點,並設定文字和圖示。QTreeWidget
中: 使用 addTopLevelItem
將 "同學" 和 "陌生人" 節點新增到 QTreeWidget
的頂級。expandAll
展開所有節點,使其在初始化時可見。QTreeWidget
的大小: 使用 resize
設定 QTreeWidget
的大小。這段程式碼的主要功能是建立一個包含不同分類和子節點的樹形結構,每個節點可以有不同的圖示、文字和選擇狀態。在展示的樹形結構中,朋友和同學節點有子節點,而陌生人節點沒有子節點。這個範例展示了 QTreeWidget
用於建立層次結構的基本用法。
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->treeWidget->clear();
// ----------------------------------------------
// 初始化TreeWidget元件
// ----------------------------------------------
// 設定QTreeWidget的列數
ui->treeWidget->setColumnCount(1);
// 設定QTreeWidget標題隱藏
ui->treeWidget->setHeaderHidden(true);
// ----------------------------------------------
// 建立QTreeWidget的朋友節點 此時的父節點是TreeWidget
// ----------------------------------------------
QTreeWidgetItem *Friend = new QTreeWidgetItem(ui->treeWidget,QStringList(QString("朋友")));
Friend->setIcon(0,QIcon(":/image/4.ico"));
Friend->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable
| Qt::ItemIsEnabled | Qt::ItemIsAutoTristate);
Friend->setCheckState(0,Qt::Checked);
// 給Friend新增一個子節點frd
QTreeWidgetItem *frd = new QTreeWidgetItem(Friend);
frd->setText(0,"老張");
frd->setIcon(0,QIcon(tr(":/image/1.ico")));
frd->setCheckState(0,Qt::Checked);
// 繼續給Friend新增一個子節點frs
QTreeWidgetItem *frs = new QTreeWidgetItem(Friend);
frs->setText(0,"老王");
frs->setIcon(0,QIcon(tr(":/image/1.ico")));
frs->setCheckState(0,Qt::Unchecked);
// ----------------------------------------------
// 繼續建立名叫同學節點 父節點同樣是TreeWidget
// ----------------------------------------------
QTreeWidgetItem * ClassMate = new QTreeWidgetItem(ui->treeWidget,QStringList(QString("同學")));
ClassMate->setIcon(0,QIcon(":/image/5.ico"));
ClassMate->setCheckState(0,Qt::Checked);
// Fly是ClassMate的子節點
QTreeWidgetItem *Fly = new QTreeWidgetItem(QStringList(QString("張三")));
Fly->setIcon(0,QIcon(tr(":/image/2.ico")));
// 建立子節點的另一種方法
ClassMate->addChild(Fly);
Fly->setCheckState(0,Qt::Checked);
// 繼續建立子節點Fls
QTreeWidgetItem *Fls = new QTreeWidgetItem(QStringList(QString("李四")));
Fls->setIcon(0,QIcon(tr(":/image/2.ico")));
ClassMate->addChild(Fls);
Fls->setCheckState(0,Qt::Checked); // 設定為選中
// ----------------------------------------------
// 建立陌生人節點
// ----------------------------------------------
QTreeWidgetItem *Strange = new QTreeWidgetItem(true);
Strange->setText(0,"陌生人");
Strange->setIcon(0,QIcon(":/image/6.ico"));
ui->treeWidget->addTopLevelItem(ClassMate);
ui->treeWidget->addTopLevelItem(Strange);
// 展開QTreeWidget的所有節點
ui->treeWidget->expandAll();
ui->treeWidget->resize(271,401);
}
程式碼執行後可動態對左側元件進行初始化,並增加應有的父節點與子節點,如下圖;
如下槽函數,其核心功能是在 QTreeWidget
中新增一個新的頂級父節點,並在 QPlainTextEdit
中新增一行文字記錄。
以下是概述:
QString NodeText = "新的父節點";
設定新父節點的文字。QTreeWidgetItem
: 使用 QTreeWidgetItem
的建構函式建立一個新的頂級父節點,並設定其文字和圖示。QTreeWidget
中: 使用 ui->treeWidget->addTopLevelItem(item);
將新的頂級父節點新增到 QTreeWidget
中。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("新增新的父節點");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,在 QTreeWidget
中新增一個新的頂級父節點,並在 QPlainTextEdit
中記錄這一操作。這樣可以用於在介面上動態新增樹節點,並記錄相關的操作資訊。
void MainWindow::on_pushButton_add_clicked()
{
QString NodeText = "新的父節點";
QTreeWidgetItem *item = new QTreeWidgetItem(true);
item->setText(0,NodeText);
item->setIcon(0,QIcon(":/image/7.ico"));
ui->treeWidget->addTopLevelItem(item);
ui->plainTextEdit->appendPlainText("新增新的父節點");
}
執行後通過點選新增根節點按鈕,每次則可以生成一個根,如下圖;
如下槽函數,其核心功能是在 QTreeWidget
中新增新的子節點,並在 QPlainTextEdit
中新增一行文字記錄。
以下是概述:
QTreeWidgetItem * item= ui->treeWidget->currentItem();
獲取當前在 QTreeWidget
中選擇的節點。if(item!=NULL)
條件判斷,如果存在選擇的節點,則呼叫 AddTreeNode
函數新增子節點;否則,呼叫 AddTreeRoot
函數新增新的根節點。AddTreeNode(item,"新子節點","新子節點");
新增一個新的子節點,其文字和圖示分別為 "新子節點"。AddTreeRoot("新子節點","新子節點");
新增一個新的根節點,其文字和圖示同樣為 "新子節點"。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("新增新的子節點");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,根據使用者當前選擇的節點狀態,在 QTreeWidget
中新增新的子節點或新的根節點,並記錄這一操作到 QPlainTextEdit
中。
QTreeWidgetItem * MainWindow::AddTreeRoot(QString name,QString desc)
{
QTreeWidgetItem * item=new QTreeWidgetItem(QStringList()<<name<<desc);
ui->treeWidget->addTopLevelItem(item);
return item;
}
QTreeWidgetItem * MainWindow::AddTreeNode(QTreeWidgetItem *parent,QString name,QString desc)
{
QTreeWidgetItem * item=new QTreeWidgetItem(QStringList()<<name<<desc);
parent->addChild(item);
return item;
}
void MainWindow::on_pushButton_addsubnode_clicked()
{
QTreeWidgetItem * item= ui->treeWidget->currentItem();
if(item!=NULL)
AddTreeNode(item,"新子節點","新子節點");
else
AddTreeRoot("新子節點","新子節點");
ui->plainTextEdit->appendPlainText("新增新的子節點");
}
子節點的新增依賴於封裝好的兩個AddTreeNode
函數,通過呼叫後則可以在父節點上新增子節點,如下圖;
如下槽函數,其核心功能是修改 QTreeWidget
中當前選中節點的文字和圖示,並在 QPlainTextEdit
中新增一行文字記錄。
以下是概述:
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem();
獲取當前在 QTreeWidget
中選擇的節點。if(currentItem == NULL)
條件判斷,如果沒有選擇的節點,則直接返回。for
迴圈遍歷節點的所有列,通過 setText
修改每一列的文字為 "Modify" 加上列索引的字串,通過 setIcon
修改每一列的圖示為特定的圖示。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("修改節點名");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,修改 QTreeWidget
中當前選中節點的文字和圖示,同時在 QPlainTextEdit
中記錄這一修改操作。
void MainWindow::on_pushButton_modifynode_clicked()
{
// 得到當前節點
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem();
if(currentItem == NULL)
return;
// 修改選中項
for(int x=0;x<currentItem->columnCount();x++)
{
currentItem->setText(x,tr("Modify") + QString::number(x));
currentItem->setIcon(x,QIcon(":/image/1.ico"));
}
ui->plainTextEdit->appendPlainText("修改節點名");
}
修改節點的執行效果如下圖,當點選修改選中節點後則將自動替換節點名和圖示資訊。
如下槽函數,其核心功能是刪除 QTreeWidget
中當前選中節點,並在 QPlainTextEdit
中新增一行文字記錄。
以下是概述:
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem();
獲取當前在 QTreeWidget
中選擇的節點。if(currentItem == NULL)
條件判斷,如果沒有選擇的節點,則直接返回。if(currentItem->parent() == NULL)
條件判斷,如果當前節點沒有父節點(即為頂級父節點),則使用 ui->treeWidget->takeTopLevelItem
刪除該節點。takeChild
刪除子節點: 使用 delete currentItem->parent()->takeChild(ui->treeWidget->currentIndex().row());
刪除當前節點。這種情況下,要使用父節點的 takeChild
方法,因為直接刪除會導致父節點無法正確管理子節點。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("刪除一個節點");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,刪除 QTreeWidget
中當前選中的節點,並記錄這一刪除操作到 QPlainTextEdit
中。
void MainWindow::on_pushButton_delnode_clicked()
{
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem();
if(currentItem == NULL)
return;
// 如果沒有父節點則直接刪除
if(currentItem->parent() == NULL)
{
delete ui->treeWidget->takeTopLevelItem(ui->treeWidget->currentIndex().row());
std::cout << ui->treeWidget->currentIndex().row() << std::endl;
}
else
{
// 如果有父節點就要用父節點的takeChild刪除節點
delete currentItem->parent()->takeChild(ui->treeWidget->currentIndex().row());
}
ui->plainTextEdit->appendPlainText("刪除一個節點");
}
刪除節點有兩種情況,如果只有父節點那麼可以直接刪除,如果有子節點則那就要一併刪除,如下圖;
如下槽函數,其核心功能是遍歷 QTreeWidget
中的所有節點,並輸出每個節點的文字。
以下是概述:
int size = ui->treeWidget->topLevelItemCount();
獲取頂級父節點的數量。for
迴圈遍歷每一個根節點,通過 ui->treeWidget->topLevelItem(x)
獲取當前的根節點。child->text(0).toStdString().data()
輸出當前根節點的文字資訊,並將其輸出到標準輸出流。for
迴圈遍歷當前根節點下的所有子節點,通過 child->child(y)
獲取子節點。grandson->text(0).toStdString().data()
輸出當前子節點的文字資訊,並將其輸出到標準輸出流。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("列舉所有節點");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,遍歷 QTreeWidget
中的所有節點,輸出每個節點的文字資訊,並將資訊記錄到 QPlainTextEdit
中。
void MainWindow::on_pushButton_enumnode_clicked()
{
// 獲取到全部的根節點數量
int size = ui->treeWidget->topLevelItemCount();
QTreeWidgetItem *child;
for(int x=0;x<size;x++)
{
// 輸出所有父節點
child = ui->treeWidget->topLevelItem(x);
std::cout << "all root = "<< child->text(0).toStdString().data() << std::endl;
// 得到所有子節點計數
int childCount = child->childCount();
// std::cout << "all child count = " << childCount << std::endl;
// 輸出根節點下面的子節點
for(int y=0;y<childCount;++y)
{
QTreeWidgetItem *grandson = child->child(y);
std::cout << "--> sub child = "<< grandson->text(0).toStdString().data() << std::endl;
ui->plainTextEdit->appendPlainText(grandson->text(0).toStdString().data());
}
}
ui->plainTextEdit->appendPlainText("列舉所有節點");
}
列舉所有節點會將父節點與子節點一併輸出,如下圖;
如下槽函數,其核心功能是遍歷 QTreeWidget
中的所有節點,並輸出每個選中節點的文字資訊。
以下是概述:
int size = ui->treeWidget->topLevelItemCount();
獲取頂級父節點的數量。for
迴圈遍歷每一個根節點,通過 ui->treeWidget->topLevelItem(x)
獲取當前的根節點。for
迴圈遍歷當前根節點下的所有子節點,通過 child->child(y)
獲取子節點。if(Qt::Checked == grandson->checkState(0))
判斷當前子節點是否被選中。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("列舉所有選中節點");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,遍歷 QTreeWidget
中的所有節點,輸出每個被選中節點的文字資訊,並將資訊記錄到 QPlainTextEdit
中。
void MainWindow::on_pushButton_enumselectnode_clicked()
{
// 獲取到全部的根節點數量
int size = ui->treeWidget->topLevelItemCount();
QTreeWidgetItem *child;
for(int x=0;x<size;x++)
{
// 輸出所有父節點
child = ui->treeWidget->topLevelItem(x);
// 得到所有子節點計數
int childCount = child->childCount();
// 輸出根節點下面的子節點
for(int y=0;y<childCount;++y)
{
QTreeWidgetItem *grandson = child->child(y);
// 判斷是否選中,如果選中輸出父節點與子節點
if(Qt::Checked == grandson->checkState(0))
{
std::cout << "root -> " << child->text(0).toStdString().data()
<< "--> sub child = "<< grandson->text(0).toStdString().data() << std::endl;
ui->plainTextEdit->appendPlainText(child->text(0).toStdString().data());
ui->plainTextEdit->appendPlainText(grandson->text(0).toStdString().data());
}
}
}
ui->plainTextEdit->appendPlainText("列舉所有選中節點");
}
列舉所有選中的節點,此處需要打上對勾才會生效,如下圖;
如下槽函數,其核心功能是獲取當前選中節點的父節點(如果存在),輸出父節點的序號和名字,並將資訊記錄到 QPlainTextEdit
中。
以下是概述:
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem()->parent();
獲取當前選中節點的父節點。int root_count = ui->treeWidget->indexOfTopLevelItem(currentItem);
獲取父節點在頂級節點中的序號。if(root_count != -1)
條件判斷,如果存在父節點,執行下面的操作;否則,直接返回。child = ui->treeWidget->topLevelItem(root_count);
獲取指定序號對應的父節點。std::cout << "root Count = " << root_count << std::endl;
輸出父節點在頂級節點中的序號,以及 std::cout << "root name= "<< child->text(0).toStdString().data() << std::endl;
輸出父節點的名字。QPlainTextEdit
中: 使用 ui->plainTextEdit->appendPlainText("獲取父節點ID");
將一行文字記錄新增到 QPlainTextEdit
中,用於記錄操作。這段程式碼的作用是在點選按鈕時,獲取當前選中節點的父節點(如果存在),輸出父節點在頂級節點中的序號和名字,並將資訊記錄到 QPlainTextEdit
中。
void MainWindow::on_pushButton_getnode_clicked()
{
// 取所有的父節點
QTreeWidgetItem *currentItem = ui->treeWidget->currentItem()->parent();
int root_count = ui->treeWidget->indexOfTopLevelItem(currentItem);
std::cout << "root Count = " << root_count << std::endl;
if(root_count != -1)
{
// 指定序號對應的父節點名字
QTreeWidgetItem *child;
child = ui->treeWidget->topLevelItem(root_count);
std::cout << "root name= "<< child->text(0).toStdString().data() << std::endl;
ui->plainTextEdit->appendPlainText(child->text(0).toStdString().data());
}
ui->plainTextEdit->appendPlainText("獲取父節點ID");
}
當用戶選中一個子節點時,可通過該槽函數獲取其父節點的ID編號,如下圖;
在開發中我們經常會把它當作一個升級版的ListView
元件使用,因為ListView
每次只能顯示一列資料集,而使用TableWidget
元件顯示多列顯得不夠美觀,此時使用TreeWidget
元件顯示單層結構是最理想的方式,同時該元件同樣支援增加右鍵選單,在真正的開發中尤為常用。
首先我們在MainWindow
主表單中只保留一個treeWidget
元件,接著直接來到MainWindow
建構函式上,在該函數中我們通過動態建立一個menuBar()
並將其隱藏起來,接著將選單屬性與treeWidget
中的事件相互繫結,最後初始化填充一些測試資料,其程式碼如下;
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// -------------------------------------------------
// 初始化元件選單
// -------------------------------------------------
// 在MainWindow中使用右擊選單需要新增此項
ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
// 建立基礎頂部選單
QMenuBar *bar = menuBar();
this->setMenuBar(bar);
QMenu * fileMenu = bar->addMenu("選單1");
// 實現只隱藏選單1其他的不受影響
fileMenu->menuAction()->setVisible(false);
// 新增子選單
GetColumnAction = fileMenu->addAction("獲取列號");
GetRowDataAction = fileMenu->addAction("獲取本行資料");
GetLineAction = fileMenu->addAction("獲取行號");
// 分別設定圖示
GetColumnAction->setIcon(QIcon(":/image/1.ico"));
GetRowDataAction->setIcon(QIcon(":/image/2.ico"));
GetLineAction->setIcon(QIcon(":/image/3.ico"));
// 為子選單系結熱鍵
GetColumnAction->setShortcut(Qt::CTRL | Qt::Key_A);
GetRowDataAction->setShortcut(Qt::SHIFT | Qt::Key_S);
GetLineAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_B);
// -------------------------------------------------
// 繫結槽函數
// -------------------------------------------------
// 繫結槽函數: 獲取選中列
connect(GetColumnAction,&QAction::triggered,this,[=](){
int col = ui->treeWidget->currentColumn();
std::cout << col << std::endl;
});
// 繫結槽函數: 獲取選中的第0行的資料內容
connect(GetRowDataAction,&QAction::triggered,this,[=](){
QString msg = ui->treeWidget->currentItem()->text(0);
std::cout << msg.toStdString().data() << std::endl;
});
// 繫結槽函數: 獲取當前選中的索引值
connect(GetLineAction,&QAction::triggered,this,[=](){
int row = ui->treeWidget->currentIndex().row();
std::cout << row << std::endl;
});
// -------------------------------------------------
// 設定屬性填充資料
// -------------------------------------------------
// 設定treeWidget屬性
ui->treeWidget->setColumnCount(4); // 設定總列數
ui->treeWidget->setColumnWidth(0,300); // 設定最後一列寬度自適應
ui->treeWidget->setIndentation(1); // 設定表頭縮排為1
// 設定表頭資料
QStringList headers;
headers.append("檔名");
headers.append("更新時間");
headers.append("檔案型別");
headers.append("檔案大小");
ui->treeWidget->setHeaderLabels(headers);
// 模擬插入資料到表中
for(int x=0;x<100;x++)
{
QTreeWidgetItem* item=new QTreeWidgetItem();
item->setText(0,"《LyShark 從入門到精通》");
item->setIcon(0,QIcon(":/image/1.ico"));
item->setText(1,"2023-12-17");
item->setText(2,"*.pdf");
item->setText(3,"102MB");
item->setIcon(3,QIcon(":/image/2.ico"));
ui->treeWidget->addTopLevelItem(item);
}
}
此時,當treeWidget
中的右鍵被點選後則將觸發on_treeWidget_customContextMenuRequested
槽函數,此函數中動態的新建一個選單,並在滑鼠點選位置將其顯示輸出,程式碼如下;
// 當treeWidget中的右鍵被點選時則觸發
void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
std::cout << "x pos = "<< pos.x() << "y pos = " << pos.y() << std::endl;
Q_UNUSED(pos);
// 新建Menu選單
QMenu *ptr = new QMenu(this);
// 新增Actions建立選單項
ptr->addAction(GetColumnAction);
ptr->addAction(GetLineAction);
// 新增一個分割線
ptr->addSeparator();
ptr->addAction(GetRowDataAction);
// 在滑鼠遊標位置顯示右鍵快捷選單
ptr->exec(QCursor::pos());
// 手工建立的指標必須手工刪除
delete ptr;
}
執行後讀者可看到如下圖所示的輸出效果;