【Qt6】列表模型——幾個便捷的列表類型

前面一些文章,老周簡單介紹了在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 類構造函數的列表元素個數不同罷了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章