【Qt6】列表模型——几个便捷的列表类型

打印 上一主题 下一主题

主题 928|帖子 928|积分 2784

前面一些文章,老周简单介绍了在Qt 中使用列表模型的方法。很明显,使用 Item Model 在许多时候还是挺麻烦的——要先建模型,再放数据,最后才构建视图。为了简化这些骚操作,Qt 提供了几个便捷类。今天咱们逐个看看。
一、QListWidget

 这厮对应的 List View,用来显示简单的列表。要添加列表项,此类有两个方法
  1. void addItem(const QString &label) ;
  2. void addItem(QListWidgetItem *item);
  3. void addItems(const QStringList &labels);
复制代码
前两个方法是调用一次就添加一个列表项,新加的列表项将追加到列表末尾;addItems 方法是一次性添加多个项,以字符串列表的方式添加。
对于简单的列表项,可以用第一个方法,直接传个字符串就完事了。第二个方法需要一个 QListWidgetItem 对象,它可以对列表项做一些其他设置,如放个图标,文本对齐方式等。当然,如果你不想用追加模式添加列表项,也可以用插入方法:
  1. void insertItem(int row, const QString &label);
  2. void insertItem(int row, QListWidgetItem *item);
  3. void insertItems(int row, const QStringList &labels);
复制代码
和 addItem 方法一样,但多了一个 row 参数。因为简单的列表模型只有一个列,所以 row 就是子项的索引。索引从 0 起计算,指定 row 参数表示在此处插入列表项,而列表中原有的元素会向后移一位。比如 5、6、7,在row=1 处插入9,即列表变成 5、9、6、7。
要删除列表项,调用 takeItem 方法。
  1. QListWidgetItem* takeItem(int row)
复制代码
调用后,指定索引处的项被移除,并返回该项的实例引用(指针类型)。不要调用 removeItemWidget 方法,那个只删除用于显示列表项的组件罢了,列表项并未真正删除。
下面咱们动动手,做个练习。
  1. int main(int argc, char **argv)
  2. {
  3.     QApplication app(argc, argv);
  4.     // 实例化组件
  5.     QListWidget *view = nullptr;
  6.     view = new QListWidget;
  7.     // 窗口标题
  8.     view->setWindowTitle("烧烤档常见食物");
  9.     // 窗口大小
  10.     view->resize(255, 200);
  11.     // 添加点子项
  12.     view->addItem("烤羊肺");
  13.     view->addItem("烤年糕");
  14.     // 选创建QListWidgetItem实例,再添加
  15.     QListWidgetItem* item = new QListWidgetItem("烤狗腿");
  16.     view->addItem(item);// 也可以用字符串列表
  17.     QStringList strs;
  18.     strs << "臭豆腐" << "烤鸭肉" << "烤鸡翅";
  19.     view->addItems(strs);
  20.     // 显示窗口
  21.     view->show();
  22.     // 进入事件循环
  23.     return QApplication::exec();
  24. }
复制代码
setSizeHint 方法为项目的“期望”大小设置一个固定的高度,宽度为0表示由布局行为决定;而 32 是高度,告诉容器组件:“我需要32的高度”,毕竟默认的高度可能显示不全按钮。最后,我用 setFixedWidth 方法把三个按钮的宽度给固死了,它的宽度只能是25像素。这么做是为了让大伙看清楚,我们自定义的组件其实就是显示在原有列表项上面的。如下图,你看看,原列表项的文本还在呢。

不过,上述代码只是方便理解,没什么实际用处。下面咱们做有用的,重新做一下这个示例。
  1. // 获取最后三项的引用
  2. int len = view->count();  // 猜猜它返回什么
  3. QListWidgetItem* tmpItem1 = view->item(len - 3);
  4. QListWidgetItem* tmpItem2 = view->item(len - 2);
  5. QListWidgetItem* tmpItem3 = view->item(len - 1);
  6. // 弄三个按钮
  7. QPushButton* b1 = new QPushButton;
  8. b1->setText("A");
  9. QPushButton* b2= new QPushButton;
  10. b2->setText("B");
  11. QPushButton* b3 = new QPushButton;
  12. b3->setText("C");
  13. // 调整一下列表项的高度,不然按钮可能显示不全
  14. QSize size(0, 32);
  15. tmpItem1->setSizeHint(size);
  16. tmpItem2->setSizeHint(size);
  17. tmpItem3->setSizeHint(size);
  18. // 设置三个按钮与三个列表项关联
  19. view->setItemWidget(tmpItem1, b1);
  20. view->setItemWidget(tmpItem2, b2);
  21. view->setItemWidget(tmpItem3, b3);
  22. // 为了能发现其中的秘密,咱们让按钮的宽度缩小一点
  23. b1->setFixedWidth(25);
  24. b2->setFixedWidth(25);
  25. b3->setFixedWidth(25);
复制代码
这里实现的逻辑是:当列表项被选中才显示按钮。现在咱们也知道,setItemWidget 是创建一个自定义组件覆盖在列表项上的,所以,在列表项选中后,显示的自定义组件的背景不能透明,不然就穿帮了。组件里面放一个QLabel 组件显示列表项的文本,然后再放一个“删除”按钮,这样就差不多了。
需要用到 QListWidget 的一个信号:
  1. view->connect(view, &QListWidget::currentItemChanged, [=]
  2. (QListWidgetItem* current, QListWidgetItem* previous) -> void{
  3.     // 删除前一个列表项所关联的QWidget
  4.     if(previous)
  5.     {
  6.         view->removeItemWidget(previous);
  7.         // 还原列表项高度
  8.         previous->setSizeHint(QSize(-1, -1));
  9.     }
  10.     // 如当前项为NULL,那后面的代码就没必要运行了
  11.     if(!current){
  12.         return;
  13.     }
  14.     // 为当前项创建QWidget
  15.     QWidget* wg = new QWidget;
  16.     // 设置背景色
  17.     wg->setAutoFillBackground(true);
  18.     wg->setStyleSheet("background: lightgray");
  19.     // 布局
  20.     QHBoxLayout* layout=new QHBoxLayout;
  21.     wg->setLayout(layout);
  22.     // 标签
  23.     QLabel* lb=new QLabel;
  24.     // 标签的文本就是列表项的文本
  25.     lb->setText(current->text());
  26.     layout->addWidget(lb);
  27.     // 加个空白,填补剩余空间
  28.     layout->addStretch(1);
  29.     // 按钮
  30.     QPushButton* btn= new QPushButton("删除");
  31.     layout->addWidget(btn);
  32.     // 套娃,又一个信号连接
  33.     QObject::connect(btn, &QPushButton::clicked, [=](){
  34.         // 当前索引
  35.         int row = view->row(current);
  36.         // 把当前列表项分离出来
  37.         QListWidgetItem* _oldItem = view->takeItem(row);
  38.         // 清除它
  39.         delete _oldItem;
  40.     });
  41.     // 要改一下列表项的高度,不然按钮可能显示不全
  42.     current->setSizeHint(QSize(0, 40));
  43.     // 关联列表项与组件
  44.     view->setItemWidget(current, wg);
  45. });
复制代码
这个信号正符合咱们的需求,current 表示当前项(99.9% 的情况下就是被选中的项),previous 是前一个项——即被取消选择的项。有了这两个参数,咱们就可以用 removeItemWidget 方法删除 previous 关联的 Widget,并为 current 关联新的 Widget。
currentItemChanged 信号连接的 lambda 表达式内部又嵌套了一个 lambda 表达式—— 连接按钮的 clicked 信号。
  1. void currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
复制代码
删除列表项时,takeItem 方法返回指定索引处的列表项指针。正因为这货需要的参数是索引,所以不得不调用 row 方法选获取 current 的索引,再传给 takeItem 方法。列表项被移除后会返回其指针,因为这时候我们不需要它了,所以得 delete 掉其指向的对象。
运行程序后,选中“臭豆腐”。

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

 
二、QTableWidget

QTableWidget 类派生自 QTableView,也是一个便捷类,可以不创建模型对象而直接添加数据。对应的列表项类型是 QTableWidgetItem。注意,一个 QTableWidgetItem 仅表示一个单元格的数据,所以,如果数据表格有两行两列,那么,你得向里面四个 item。
在添加列表项之前,要先调用:
setRowCount—— 设置表格共有几行;
setColumnCount—— 设置表格共有多少列。
然后设置标题。包括列标题、行标题。一般设置列标题就可以了,行标题通常不用设置(默认显示行号)。
setHorizontalHeaderLabels—— 设置列标题;
setVerticalHeaderLabels—— 设置行标题。
不管是行还是列标题,都可以使用字符串列表(QStringList)来传递,有几行/列就设置几个值。
设置完上述基本参数后,就可以用 setItem 方法来设置每个单元格的数据了。方法原型如下:
  1. QObject::connect(btn, &QPushButton::clicked, [=](){
  2.     // 当前索引
  3.     int row = view->row(current);
  4.     // 把当前列表项分离出来
  5.     QListWidgetItem* _oldItem = view->takeItem(row);
  6.     // 清除它
  7.     delete _oldItem;
  8. });
复制代码
row 表示行索引,column 表示列索引,索引从 0 算起。
 
接下来咱们做个演示:
  1. void setItem(int row, int column, QTableWidgetItem *item);
复制代码
运行结果如下:

 
三、QTreeWidget

 QTreeWidget 类针对的就是树形结构的列表,是 QTreeView 的子类。它对应的列表项是 QTreeWidgetItem 类。
不过这里要注意的是,QTreeWidgetItem 虽然表示树形数据中的一个节点,但它在形式上就像一行数据。因为它可以包含多个列。Qt 的 Tree 视图是可以展示多列的。
下面咱们先做个单列的 Tree 视图。
  1. int main(int argc, char* argv[])
  2. {
  3.     QApplication app(argc, argv);
  4.     // 创建组件实例
  5.     QTableWidget* viewWindow = new QTableWidget;
  6.     // 设置标题和大小
  7.     viewWindow->setWindowTitle("经典语录");
  8.     viewWindow->resize(350, 270);
  9.     // 设定行数和列数
  10.     viewWindow->setColumnCount(3);  // 三列
  11.     viewWindow->setRowCount(4);     // 四行
  12.     // 先弄好列标题
  13.     viewWindow->setHorizontalHeaderLabels({"编号", "句子", "伤害指数"});
  14.     // 创建列表项
  15.     // 注意,每个QTableWidgetItem代表一个单元格
  16.     // 第一行
  17.     viewWindow->setItem(0, 0, new QTableWidgetItem("001"));
  18.     viewWindow->setItem(0, 1, new QTableWidgetItem("你就长这个样子啊?"));
  19.     viewWindow->setItem(0, 2, new QTableWidgetItem("2"));
  20.     // 第二行
  21.     viewWindow->setItem(1, 0, new QTableWidgetItem("002"));
  22.     viewWindow->setItem(1, 1, new QTableWidgetItem("你进化到灵长目动物了吗?"));
  23.     viewWindow->setItem(1, 2, new QTableWidgetItem("3"));
  24.     // 第三行
  25.     viewWindow->setItem(2, 0, new QTableWidgetItem("003"));
  26.     viewWindow->setItem(2, 1, new QTableWidgetItem("你脑细胞够不够用?"));
  27.     viewWindow->setItem(2, 2, new QTableWidgetItem("5"));
  28.     // 第四行
  29.     viewWindow->setItem(3, 0, new QTableWidgetItem("004"));
  30.     viewWindow->setItem(3, 1, new QTableWidgetItem("学姐,你有头吗?"));
  31.     viewWindow->setItem(3, 2, new QTableWidgetItem("10"));
  32.     // 显示视图窗口
  33.     viewWindow->show();
  34.     return QApplication::exec();
  35. }
复制代码
QTreeWidgetItem 类的构造函数可以使用字符串列表来初始化显示的文本。原型如下:
  1. int main(int argc, char** argv)
  2. {
  3.     QApplication app(argc, argv);
  4.     // 创建视图窗口
  5.     QTreeWidget* view = nullptr;
  6.     view = new QTreeWidget;
  7.     // 先来几个顶层节点
  8.     QTreeWidgetItem* item1 = new QTreeWidgetItem({"秦"});
  9.     QTreeWidgetItem* item2 = new QTreeWidgetItem({"汉"});
  10.     QTreeWidgetItem* item3 = new QTreeWidgetItem({"晋"});
  11.     QTreeWidgetItem* item4 = new QTreeWidgetItem({"隋"});
  12.     QTreeWidgetItem* item5 = new QTreeWidgetItem({"唐"});
  13.     QTreeWidgetItem* item6 = new QTreeWidgetItem({"宋"});
  14.     // 给顶层节点添加子节点
  15.     // 秦朝
  16.     item1->addChild(new QTreeWidgetItem({"胡亥"}));
  17.     item1->addChild(new QTreeWidgetItem({"扶苏"}));
  18.     item1->addChild(new QTreeWidgetItem({"辛追"}));
  19.     item1->addChild(new QTreeWidgetItem({"项籍"}));
  20.     // 汉朝
  21.     item2->addChildren({
  22.         new QTreeWidgetItem({"霍光"}),
  23.         new QTreeWidgetItem({"刘向"}),
  24.         new QTreeWidgetItem({"司马迁"})
  25.     });
  26.     // 晋朝
  27.     item3->addChildren({
  28.         new QTreeWidgetItem({"卫铄"}),
  29.         new QTreeWidgetItem({"司马承"}),
  30.         new QTreeWidgetItem({"谢安"}),
  31.         new QTreeWidgetItem({"王导"})
  32.     });
  33.     // 隋朝
  34.     item4->addChild(new QTreeWidgetItem({"杨坚"}));
  35.     item4->addChild(new QTreeWidgetItem({"史万岁"}));
  36.     item4->addChild(new QTreeWidgetItem({"王通"}));
  37.     // 唐朝
  38.     item5->addChildren({
  39.         new QTreeWidgetItem({"上官婉儿"}),
  40.         new QTreeWidgetItem({"李龟年"}),
  41.         new QTreeWidgetItem({"张旭"}),
  42.         new QTreeWidgetItem({"杜牧"}),
  43.         new QTreeWidgetItem({"武三思"}),
  44.         new QTreeWidgetItem({"李靖"})
  45.     });
  46.     // 宋朝
  47.     item6->addChildren({
  48.         new QTreeWidgetItem({"王坚"}),
  49.         new QTreeWidgetItem({"贾似道"}),
  50.         new QTreeWidgetItem({"司马光"}),
  51.         new QTreeWidgetItem({"宋慈"}),
  52.         new QTreeWidgetItem({"张士逊"})
  53.     });
  54.     // 将顶层节点添加到QTreeWidget中
  55.     view->addTopLevelItems({
  56.         item1,
  57.         item2,
  58.         item3,
  59.         item4,
  60.         item5,
  61.         item6
  62.     });
  63.     // 隐藏标题栏
  64.     view->setHeaderHidden(true);
  65.     // 设置窗口标题
  66.     view->setWindowTitle("中华名人表");
  67.     // 显示窗口
  68.     view->show();
  69.     return QApplication::exec();
  70. }
复制代码
在赋值的时候,可以直接用 { },例如
  1. explicit QTreeWidgetItem(const QStringList &strings, int type = Type);
复制代码
在上面例子中,因为咱们这次用的只是一列,所以传一个字符串元素就可以了。
QTreeWidgetItem 要添加子节点,可以用这些方法:
  1. QTreeWidgetItem({"天时", "地利", "人和"});
复制代码
QTreeWidget 组件用以下方法添加顶层节点:
  1. // 一次只加一个节点,新节点追加到末尾
  2. void addChild(QTreeWidgetItem *child);
  3. // 一次只添加一个节点,但可以指定新节点插入到哪个位置
  4. void insertChild(int index, QTreeWidgetItem *child);
  5. // 一次可以添加多个节点,参数是个列表对象
  6. void addChildren(const QList<QTreeWidgetItem*> &children);
  7. // 一次可以添加多个节点,能指定插入位置
  8. void insertChildren(int index, const QList<QTreeWidgetItem*> &children);
复制代码
运行效果如下:

 
QTreeWidget 类也有 setItemWidget、removeItemWidget 方法。这个就不必多介绍了,和上面 QListWidget 类的一个意思,就是用一个自定义 Widget 显示在数据项上面。
下面咱们做个多列的 Tree 视图。
  1. // 添加一个节点,追加到末尾
  2. void addTopLevelItem(QTreeWidgetItem *item);
  3. // 添加一个节点,可以指定插入位置
  4. void insertTopLevelItem(int index, QTreeWidgetItem *item);
  5. // 添加多个节点,追加到末尾
  6. void addTopLevelItems(const QList<QTreeWidgetItem*> &items);
  7. // 添加多个节点,可指定插入位置
  8. void insertTopLevelItems(int index, const QList<QTreeWidgetItem*> &items);
复制代码
效果如下:

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

麻花痒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表