0

【Qt6】列表模型——几个便捷的列表类型 - 东邪独孤

 7 months ago
source link: https://www.cnblogs.com/tcjiaan/p/17780868.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前面一些文章,老周简单介绍了在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> 类。

效果如下图所示:

367389-20231025215840065-1158527645.png

前面我们提到过一个 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像素。这么做是为了让大伙看清楚,我们自定义的组件其实就是显示在原有列表项上面的。如下图,你看看,原列表项的文本还在呢。

367389-20231025222358117-380719381.png

不过,上述代码只是方便理解,没什么实际用处。下面咱们做有用的,重新做一下这个示例。

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 掉其指向的对象。

运行程序后,选中“臭豆腐”。

367389-20231029110339845-1827461372.png

然后单击右边的“删除”按钮,臭豆腐就没了。

367389-20231029110443829-1131062341.png

二、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();
}

运行结果如下:

367389-20231029115655205-428182628.png

三、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);

运行效果如下:

367389-20231029174335436-1443904954.png

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();
}

效果如下:

367389-20231029184640254-436601765.png

代码就不用多解释了吧,单列和多列的节点用法是一样的,只是传递给 QTreeWidgetItem 类构造函数的列表元素个数不同罢了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK