前面一些文章,老周簡單介紹了在Qt 中使用列表模型的方法。很明顯,使用 Item Model 在許多時候還是挺麻煩的——要先建模型,再放資料,最後才構建檢視。為了簡化這些騷操作,Qt 提供了幾個便捷類。今天我們們逐個看看。
一、QListWidget
這廝對應的 List View,用來顯示簡單的列表。要新增列表項,此類有兩個方法
void addItem(const QString &label) ; void addItem(QListWidgetItem *item); void addItems(const QStringList &labels);
前兩個方法是呼叫一次就新增一個列表項,新加的列表項將追加到列表末尾;addItems 方法是一次性新增多個項,以字串列表的方式新增。
對於簡單的列表項,可以用第一個方法,直接傳個字串就完事了。第二個方法需要一個 QListWidgetItem 物件,它可以對列表項做一些其他設定,如放個圖示,文字對齊方式等。當然,如果你不想用追加模式新增列表項,也可以用插入方法:
void insertItem(int row, const QString &label); void insertItem(int row, QListWidgetItem *item); void insertItems(int row, const QStringList &labels);
和 addItem 方法一樣,但多了一個 row 引數。因為簡單的列表模型只有一個列,所以 row 就是子項的索引。索引從 0 起計算,指定 row 參數列示在此處插入列表項,而列表中原有的元素會向後移一位。比如 5、6、7,在row=1 處插入9,即列表變成 5、9、6、7。
要刪除列表項,呼叫 takeItem 方法。
QListWidgetItem* takeItem(int row)
呼叫後,指定索引處的項被移除,並返回該項的例項引用(指標型別)。不要呼叫 removeItemWidget 方法,那個只刪除用於顯示列表項的元件罷了,列表項並未真正刪除。
下面我們們動動手,做個練習。
int main(int argc, char **argv) { QApplication app(argc, argv); // 例項化元件 QListWidget *view = nullptr; view = new QListWidget; // 視窗標題 view->setWindowTitle("燒烤檔常見食物"); // 視窗大小 view->resize(255, 200); // 新增點子項 view->addItem("烤羊肺"); view->addItem("烤年糕"); // 選建立QListWidgetItem例項,再新增 QListWidgetItem* item = new QListWidgetItem("烤狗腿"); view->addItem(item);// 也可以用字串列表 QStringList strs; strs << "臭豆腐" << "烤鴨肉" << "烤雞翅"; view->addItems(strs); // 顯示視窗 view->show(); // 進入事件迴圈 return QApplication::exec(); }
第一、二項直接用字串新增列表項;第三項是先建立 QListWidgetItem 例項,然後再新增;第四、五、六項是透過字串列表一次性新增的。QStringList 其實就是 QList<QString> 類。
效果如下圖所示:
前面我們提到過一個 removeItemWidget 方法。提到它就得提一下 setItemWidget 方法,因為這倆是青梅竹馬的。這一對方法的作用是為某個列表項新增(或刪除)一個自定義元件(QWidget 或其子類)。被新增的元件會顯示在該列表項上面——其實是覆蓋在原有內容上面的。這個方法雖然方便我們為列表項定製 UI,但它不支援動態行為,比如,你如果加一個文字框用來編輯資料,編輯好後資料是不會自動儲存的,所有的邏輯都要你自己寫程式碼實現。
我們們拿上面的示例開刀,為最後三項加入個按鈕,看看會怎樣。程式碼修改如下:
// 獲取最後三項的引用 int len = view->count(); // 猜猜它返回什麼 QListWidgetItem* tmpItem1 = view->item(len - 3); QListWidgetItem* tmpItem2 = view->item(len - 2); QListWidgetItem* tmpItem3 = view->item(len - 1); // 弄三個按鈕 QPushButton* b1 = new QPushButton; b1->setText("A"); QPushButton* b2= new QPushButton; b2->setText("B"); QPushButton* b3 = new QPushButton; b3->setText("C"); // 調整一下列表項的高度,不然按鈕可能顯示不全 QSize size(0, 32); tmpItem1->setSizeHint(size); tmpItem2->setSizeHint(size); tmpItem3->setSizeHint(size); // 設定三個按鈕與三個列表項關聯 view->setItemWidget(tmpItem1, b1); view->setItemWidget(tmpItem2, b2); view->setItemWidget(tmpItem3, b3); // 為了能發現其中的秘密,我們們讓按鈕的寬度縮小一點 b1->setFixedWidth(25); b2->setFixedWidth(25); b3->setFixedWidth(25);
setSizeHint 方法為專案的“期望”大小設定一個固定的高度,寬度為0表示由佈局行為決定;而 32 是高度,告訴容器元件:“我需要32的高度”,畢竟預設的高度可能顯示不全按鈕。最後,我用 setFixedWidth 方法把三個按鈕的寬度給固死了,它的寬度只能是25畫素。這麼做是為了讓大夥看清楚,我們自定義的元件其實就是顯示在原有列表項上面的。如下圖,你看看,原列表項的文字還在呢。
不過,上述程式碼只是方便理解,沒什麼實際用處。下面我們們做有用的,重新做一下這個示例。
view->connect(view, &QListWidget::currentItemChanged, [=] (QListWidgetItem* current, QListWidgetItem* previous) -> void{ // 刪除前一個列表項所關聯的QWidget if(previous) { view->removeItemWidget(previous); // 還原列表項高度 previous->setSizeHint(QSize(-1, -1)); } // 如當前項為NULL,那後面的程式碼就沒必要執行了 if(!current){ return; } // 為當前項建立QWidget QWidget* wg = new QWidget; // 設定背景色 wg->setAutoFillBackground(true); wg->setStyleSheet("background: lightgray"); // 佈局 QHBoxLayout* layout=new QHBoxLayout; wg->setLayout(layout); // 標籤 QLabel* lb=new QLabel; // 標籤的文字就是列表項的文字 lb->setText(current->text()); layout->addWidget(lb); // 加個空白,填補剩餘空間 layout->addStretch(1); // 按鈕 QPushButton* btn= new QPushButton("刪除"); layout->addWidget(btn); // 套娃,又一個訊號連線 QObject::connect(btn, &QPushButton::clicked, [=](){ // 當前索引 int row = view->row(current); // 把當前列表項分離出來 QListWidgetItem* _oldItem = view->takeItem(row); // 清除它 delete _oldItem; }); // 要改一下列表項的高度,不然按鈕可能顯示不全 current->setSizeHint(QSize(0, 40)); // 關聯列表項與元件 view->setItemWidget(current, wg); });
這裡實現的邏輯是:當列表項被選中才顯示按鈕。現在我們們也知道,setItemWidget 是建立一個自定義元件覆蓋在列表項上的,所以,在列表項選中後,顯示的自定義元件的背景不能透明,不然就穿幫了。元件裡面放一個QLabel 元件顯示列表項的文字,然後再放一個“刪除”按鈕,這樣就差不多了。
需要用到 QListWidget 的一個訊號:
void currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
這個訊號正符合我們們的需求,current 表示當前項(99.9% 的情況下就是被選中的項),previous 是前一個項——即被取消選擇的項。有了這兩個引數,我們們就可以用 removeItemWidget 方法刪除 previous 關聯的 Widget,併為 current 關聯新的 Widget。
currentItemChanged 訊號連線的 lambda 表示式內部又巢狀了一個 lambda 表示式—— 連線按鈕的 clicked 訊號。
QObject::connect(btn, &QPushButton::clicked, [=](){ // 當前索引 int row = view->row(current); // 把當前列表項分離出來 QListWidgetItem* _oldItem = view->takeItem(row); // 清除它 delete _oldItem; });
刪除列表項時,takeItem 方法返回指定索引處的列表項指標。正因為這貨需要的引數是索引,所以不得不呼叫 row 方法選獲取 current 的索引,再傳給 takeItem 方法。列表項被移除後會返回其指標,因為這時候我們不需要它了,所以得 delete 掉其指向的物件。
執行程式後,選中“臭豆腐”。
然後單擊右邊的“刪除”按鈕,臭豆腐就沒了。
二、QTableWidget
QTableWidget 類派生自 QTableView,也是一個便捷類,可以不建立模型物件而直接新增資料。對應的列表項型別是 QTableWidgetItem。注意,一個 QTableWidgetItem 僅表示一個單元格的資料,所以,如果資料表格有兩行兩列,那麼,你得向裡面四個 item。
在新增列表項之前,要先呼叫:
setRowCount—— 設定表格共有幾行;
setColumnCount—— 設定表格共有多少列。
然後設定標題。包括列標題、行標題。一般設定列標題就可以了,行標題通常不用設定(預設顯示行號)。
setHorizontalHeaderLabels—— 設定列標題;
setVerticalHeaderLabels—— 設定行標題。
不管是行還是列標題,都可以使用字串列表(QStringList)來傳遞,有幾行/列就設定幾個值。
設定完上述基本引數後,就可以用 setItem 方法來設定每個單元格的資料了。方法原型如下:
void setItem(int row, int column, QTableWidgetItem *item);
row 表示行索引,column 表示列索引,索引從 0 算起。
接下來我們們做個演示:
int main(int argc, char* argv[]) { QApplication app(argc, argv); // 建立元件例項 QTableWidget* viewWindow = new QTableWidget; // 設定標題和大小 viewWindow->setWindowTitle("經典語錄"); viewWindow->resize(350, 270); // 設定行數和列數 viewWindow->setColumnCount(3); // 三列 viewWindow->setRowCount(4); // 四行 // 先弄好列標題 viewWindow->setHorizontalHeaderLabels({"編號", "句子", "傷害指數"}); // 建立列表項 // 注意,每個QTableWidgetItem代表一個單元格 // 第一行 viewWindow->setItem(0, 0, new QTableWidgetItem("001")); viewWindow->setItem(0, 1, new QTableWidgetItem("你就長這個樣子啊?")); viewWindow->setItem(0, 2, new QTableWidgetItem("2")); // 第二行 viewWindow->setItem(1, 0, new QTableWidgetItem("002")); viewWindow->setItem(1, 1, new QTableWidgetItem("你進化到靈長目動物了嗎?")); viewWindow->setItem(1, 2, new QTableWidgetItem("3")); // 第三行 viewWindow->setItem(2, 0, new QTableWidgetItem("003")); viewWindow->setItem(2, 1, new QTableWidgetItem("你腦細胞夠不夠用?")); viewWindow->setItem(2, 2, new QTableWidgetItem("5")); // 第四行 viewWindow->setItem(3, 0, new QTableWidgetItem("004")); viewWindow->setItem(3, 1, new QTableWidgetItem("學姐,你有頭嗎?")); viewWindow->setItem(3, 2, new QTableWidgetItem("10")); // 顯示檢視視窗 viewWindow->show(); return QApplication::exec(); }
執行結果如下:
三、QTreeWidget
QTreeWidget 類針對的就是樹形結構的列表,是 QTreeView 的子類。它對應的列表項是 QTreeWidgetItem 類。
不過這裡要注意的是,QTreeWidgetItem 雖然表示樹形資料中的一個節點,但它在形式上就像一行資料。因為它可以包含多個列。Qt 的 Tree 檢視是可以展示多列的。
下面我們們先做個單列的 Tree 檢視。
int main(int argc, char** argv) { QApplication app(argc, argv); // 建立檢視視窗 QTreeWidget* view = nullptr; view = new QTreeWidget; // 先來幾個頂層節點 QTreeWidgetItem* item1 = new QTreeWidgetItem({"秦"}); QTreeWidgetItem* item2 = new QTreeWidgetItem({"漢"}); QTreeWidgetItem* item3 = new QTreeWidgetItem({"晉"}); QTreeWidgetItem* item4 = new QTreeWidgetItem({"隋"}); QTreeWidgetItem* item5 = new QTreeWidgetItem({"唐"}); QTreeWidgetItem* item6 = new QTreeWidgetItem({"宋"}); // 給頂層節點新增子節點 // 秦朝 item1->addChild(new QTreeWidgetItem({"胡亥"})); item1->addChild(new QTreeWidgetItem({"扶蘇"})); item1->addChild(new QTreeWidgetItem({"辛追"})); item1->addChild(new QTreeWidgetItem({"項籍"})); // 漢朝 item2->addChildren({ new QTreeWidgetItem({"霍光"}), new QTreeWidgetItem({"劉向"}), new QTreeWidgetItem({"司馬遷"}) }); // 晉朝 item3->addChildren({ new QTreeWidgetItem({"衛鑠"}), new QTreeWidgetItem({"司馬承"}), new QTreeWidgetItem({"謝安"}), new QTreeWidgetItem({"王導"}) }); // 隋朝 item4->addChild(new QTreeWidgetItem({"楊堅"})); item4->addChild(new QTreeWidgetItem({"史萬歲"})); item4->addChild(new QTreeWidgetItem({"王通"})); // 唐朝 item5->addChildren({ new QTreeWidgetItem({"上官婉兒"}), new QTreeWidgetItem({"李龜年"}), new QTreeWidgetItem({"張旭"}), new QTreeWidgetItem({"杜牧"}), new QTreeWidgetItem({"武三思"}), new QTreeWidgetItem({"李靖"}) }); // 宋朝 item6->addChildren({ new QTreeWidgetItem({"王堅"}), new QTreeWidgetItem({"賈似道"}), new QTreeWidgetItem({"司馬光"}), new QTreeWidgetItem({"宋慈"}), new QTreeWidgetItem({"張士遜"}) }); // 將頂層節點新增到QTreeWidget中 view->addTopLevelItems({ item1, item2, item3, item4, item5, item6 }); // 隱藏標題欄 view->setHeaderHidden(true); // 設定視窗標題 view->setWindowTitle("中華名人表"); // 顯示視窗 view->show(); return QApplication::exec(); }
QTreeWidgetItem 類的建構函式可以使用字串列表來初始化顯示的文字。原型如下:
explicit QTreeWidgetItem(const QStringList &strings, int type = Type);
在賦值的時候,可以直接用 { },例如
QTreeWidgetItem({"天時", "地利", "人和"});
在上面例子中,因為我們們這次用的只是一列,所以傳一個字串元素就可以了。
QTreeWidgetItem 要新增子節點,可以用這些方法:
// 一次只加一個節點,新節點追加到末尾 void addChild(QTreeWidgetItem *child); // 一次只新增一個節點,但可以指定新節點插入到哪個位置 void insertChild(int index, QTreeWidgetItem *child); // 一次可以新增多個節點,引數是個列表物件 void addChildren(const QList<QTreeWidgetItem*> &children); // 一次可以新增多個節點,能指定插入位置 void insertChildren(int index, const QList<QTreeWidgetItem*> &children);
QTreeWidget 元件用以下方法新增頂層節點:
// 新增一個節點,追加到末尾 void addTopLevelItem(QTreeWidgetItem *item); // 新增一個節點,可以指定插入位置 void insertTopLevelItem(int index, QTreeWidgetItem *item); // 新增多個節點,追加到末尾 void addTopLevelItems(const QList<QTreeWidgetItem*> &items); // 新增多個節點,可指定插入位置 void insertTopLevelItems(int index, const QList<QTreeWidgetItem*> &items);
執行效果如下:
QTreeWidget 類也有 setItemWidget、removeItemWidget 方法。這個就不必多介紹了,和上面 QListWidget 類的一個意思,就是用一個自定義 Widget 顯示在資料項上面。
下面我們們做個多列的 Tree 檢視。
#include <qapplication.h> #include <qtreewidget.h> int main(int argc, char* argv[]) { QApplication app(argc, argv); QTreeWidget * view = new QTreeWidget; // 設定列數 view->setColumnCount(4); // 頂層節點 auto itemA = new QTreeWidgetItem({"潛水部"}); auto itemB = new QTreeWidgetItem({"洗腦部"}); auto itemC = new QTreeWidgetItem({"韭菜部"}); // 子節點 itemA->addChildren({ new QTreeWidgetItem({"01", "梁酷襠", "經理", "6"}), new QTreeWidgetItem({"02", "王曉意", "秘書", "3"}) }); itemB->addChildren({ new QTreeWidgetItem({"03", "於三明", "經理", "4"}), new QTreeWidgetItem({"04", "週日清", "經理助理", "3"}), new QTreeWidgetItem({"05", "費潔合", "跟辦", "5"}) }); itemC->addChildren({ new QTreeWidgetItem({"06", "安德詩", "經理", "3"}), new QTreeWidgetItem({"07", "李殿馳", "助理", "2"}), new QTreeWidgetItem({"08", "易更菁", "文員", "3"}) }); // 新增頂層節點到檢視 view->addTopLevelItems({itemA, itemB, itemC}); // 設定列標題 view->setHeaderLabels({"編號", "姓名", "職務", "工齡"}); // 設定視窗標題 view->setWindowTitle("啃瓜裝飾服務有限公司員工表"); // 顯示視窗 view->show(); return QApplication::exec(); }
效果如下:
程式碼就不用多解釋了吧,單列和多列的節點用法是一樣的,只是傳遞給 QTreeWidgetItem 類建構函式的列表元素個數不同罷了。