Qt常用控件指南(8)

Qt核心控件深度解析:表格、树形结构与标签页的高级应用

在图形用户界面(GUI)开发中,数据的结构化展示与交互是核心需求之一。Qt框架提供了一系列功能强大的控件来满足这一需求,其中QTableWidgetQTreeWidget以及QTabWidget是处理列表数据、层级数据和多页面视图的典型代表。同时,QGroupBox作为容器控件,为界面元素的逻辑分组提供了基础支持。本文将基于具体的代码实现与UI设计,深入剖析这些控件的内部机制、初始化流程以及动态交互逻辑。

一、QTableWidget:二维数据的网格化呈现

QTableWidget是Qt提供的一种基于项(Item-based)的表格控件,它继承自QTableView。与基于模型(Model-based)的视图不同,QTableWidget内置了一个默认模型,直接通过QTableWidgetItem对象来管理每个单元格的数据与状态。这种设计使得开发者无需单独构建数据模型即可快速实现表格功能,非常适合处理中小规模的数据集合。

1.1 表格结构与单元格对象

一个完整的表格由若干行(Row)和列(Column)交织而成。在QTableWidget的体系中,每一个数据单元并非简单的字符串,而是一个QTableWidgetItem实例。该对象不仅封装了显示的文本,还包含了字体、前景色、背景色、图标以及对齐方式等视觉属性。

下图展示了一个基础的表格控件在UI设计器中的形态,此时尚未填充具体数据,仅展示了控件的边框占位。

在实际开发场景中,表格通常与其它输入或控制元素配合使用。在下方的UI布局设计中,可以看到中心区域放置了一个QTableWidget,上方配置了一个QLineEdit用于输入自定义内容,右侧则垂直排列了四个QPushButton,分别用于执行行与列的动态增删操作。

对按钮的命名规范化是良好编码习惯的基础,这里将按钮分别命名以对应其功能:插入行、删除行、插入列、删除列。

最终构建出的操作面板如下所示,清晰的布局为后续的信号与槽连接奠定了基础。

1.2 表格的初始化与维度设定

在构造函数中对表格进行初始化是构建界面的第一步。QTableWidget在填充数据前,必须先定义其网格的维度,即行数和列数。如果直接向不存在的行列索引设置数据,操作将无效。

通过调用insertRow()insertColumn()函数,可以动态地向表格中开辟空间。以下代码展示了如何在初始化阶段创建3行3列的网格结构:

cpp 复制代码
//插入三个行
ui->tableWidget->insertRow(0);
ui->tableWidget->insertRow(1);
ui->tableWidget->insertRow(2);

//插入三个列
ui->tableWidget->insertColumn(0);
ui->tableWidget->insertColumn(1);
ui->tableWidget->insertColumn(2);

执行上述代码后,界面上的表格控件会生成对应的网格线,此时单元格内尚无内容。

1.3 表头设置与数据填充

为了赋予列具体的业务含义,需要设置水平表头(Horizontal Header)。setHorizontalHeaderItem函数接受列索引和一个QTableWidgetItem对象作为参数。这里分别将三列命名为"学号"、"姓名"和"年龄"。

cpp 复制代码
//给三个列设置列名(设置水平方向的表头)
ui->tableWidget->setHorizontalHeaderItem(0,new QTableWidgetItem("学号"));
ui->tableWidget->setHorizontalHeaderItem(1,new QTableWidgetItem("姓名"));
ui->tableWidget->setHorizontalHeaderItem(2,new QTableWidgetItem("年龄"));

设置表头后,表格的语义更加清晰,如下所示:

数据填充是通过setItem函数完成的。该函数需要指定行索引、列索引以及承载数据的QTableWidgetItem指针。值得注意的是,QTableWidget接管了这些Item对象的内存管理,当表格被销毁或Item被移除时,相应的内存通常会被自动释放(除非使用了takeItem且未重新插入或手动删除)。

以下代码模拟了三组学生数据的录入过程:

cpp 复制代码
//给表格中添加数据1
ui->tableWidget->setItem(0,0,new QTableWidgetItem("1001"));
ui->tableWidget->setItem(0,1,new QTableWidgetItem("张三"));
ui->tableWidget->setItem(0,2,new QTableWidgetItem("19"));

//给表格中添加数据2
ui->tableWidget->setItem(1,0,new QTableWidgetItem("1002"));
ui->tableWidget->setItem(1,1,new QTableWidgetItem("张三"));
ui->tableWidget->setItem(1,2,new QTableWidgetItem("21"));

//给表格中添加数据3
ui->tableWidget->setItem(2,0,new QTableWidgetItem("1003"));
ui->tableWidget->setItem(2,1,new QTableWidgetItem("张三"));
ui->tableWidget->setItem(2,2,new QTableWidgetItem("20"));

初始化完成后的表格展示了完整的数据内容:

1.4 动态行列操作的逻辑实现

静态数据的展示只是基础,交互式地修改表格结构更能体现控件的灵活性。通过连接按钮的clicked信号与自定义槽函数,可以实现运行时的动态编辑。

行操作逻辑:

新增行通常在表格末尾进行。rowCount()函数返回当前的总行数。由于行索引从0开始,当前的总行数恰好是下一行的索引值,因此直接将rowCount()作为insertRow的参数即可实现在末尾追加。删除行则需要获取用户当前选中的行索引,通过currentRow()获取,随后调用removeRow()执行删除。

cpp 复制代码
void Widget::on_pushButton_insertRow_clicked()
{
    //先知道当前一共有多少行
    int rowCount=ui->tableWidget->rowCount();
    //在最后一行新增新行,rowCount是下标,所以这里我们是不需要 +1的,下标从0开始的
    ui->tableWidget->insertRow(rowCount);
}

void Widget::on_pushButton_deleteRow_clicked()
{
    //获取要 删除的行号
    int curRow=ui->tableWidget->currentRow();
    //删除这一行
    ui->tableWidget->removeRow(curRow);
}

列操作逻辑:

新增列的逻辑与新增行类似,利用columnCount()确定插入位置。不同之处在于,新增列后通常需要立即设置列头,以便用户识别该列的用途。代码中读取了lineEdit输入框中的文本,作为新列的标题。

cpp 复制代码
void Widget::on_pushButton_insertColum_clicked()
{
    //新增列的话我们需要新插入一个 空列,然后设置列头,列头就是输入框中的内容

    //先知道当前一共有多少列
    int colCount=ui->tableWidget->columnCount();
    //在最后一行新增新列,rowCount是下标,所以这里我们是不需要 +1的,下标从0开始的
    ui->tableWidget->insertColumn(colCount);

    const QString &text=ui->lineEdit->text();//拿到输入框中的文本
    ui->tableWidget->setHorizontalHeaderItem(colCount,new QTableWidgetItem(text));
}

void Widget::on_pushButton_deleteColum_clicked()
{
    //获取要 删除的列号
    int colCount=ui->tableWidget->currentColumn();
    //删除这一列
    ui->tableWidget->removeColumn(colCount);
}

通过上述逻辑,用户界面实现了对表格维度的完全控制。

二、QGroupBox:界面元素的逻辑容器

在复杂的UI布局中,QGroupBox提供了一种可视化的分组机制。它通常表现为一个带有标题的边框,将功能相关的控件框定在一起。

下图展示了一个QGroupBox的基本形态:

QGroupBox不仅仅是一个视觉装饰,它还具有父容器的属性。在下图中,QSpinBox(微调框)和QComboBox(下拉框)被放置在QGroupBox内部,成为了它的子元素。当移动Group Box时,内部的子元素会随之移动;当隐藏Group Box时,子元素也会一同隐藏。

QGroupBox具备一个名为checkable的重要属性。启用该属性后,标题栏旁边会出现一个复选框。这一功能的强大之处在于,它建立了一种整体的启用/禁用机制。当用户取消勾选该复选框时,Group Box内部的所有子控件会自动变为不可用(Disabled)状态,无需开发者逐个遍历子控件进行设置。这在配置选项的分组管理中极为实用。

三、QTreeWidget:层级数据的树状展示

QTreeWidget用于显示具有父子关系的层级数据。与QTableWidget类似,它也是基于项的控件,其中的基本单元是QTreeWidgetItem。树形控件的特点在于,每个节点(Item)不仅可以包含多列数据(文本、图标),还可以拥有任意数量的子节点,从而形成递归的树状结构。

树形控件支持多列显示,这使得它在表现文件系统、组织架构等复杂信息时游刃有余。

下图展示了其在运行时的典型外观,节点具有展开和折叠的交互能力。

3.1 树形控件的UI布局与初始化

为了演示树形控件的操作,UI界面被设计为左侧放置QTreeWidget,右侧放置功能按钮区和输入框。三个按钮分别对应:添加顶层节点、添加子节点、删除节点。

虽然可以通过UI设计器的右键菜单进行静态项目的编辑(如下所示),但在动态业务中,代码控制更为常见。

代码初始化首先设置表头标签,明确每一列的含义。接着,创建顶层节点(Top Level Item)。顶层节点直接挂载在QTreeWidget下方,没有父节点。

cpp 复制代码
//设置根节点的名字
ui->treeWidget->setHeaderLabel("动物");

//新增顶层节点
QTreeWidgetItem *item1=new QTreeWidgetItem();
//每个节点是可以设置多个列的
item1->setText(0,"猫");
ui->treeWidget->addTopLevelItem(item1);

执行上述代码后,树形控件中出现了一个名为"猫"的根节点。

可以继续添加多个顶层节点,它们在层级上是平级的。

树形结构的核心在于嵌套。通过创建新的QTreeWidgetItem并调用父节点的addChild方法,可以构建出深层的层级关系。例如,在"猫"这个分类下添加"中华田园猫"。

cpp 复制代码
//添加子节点
QTreeWidgetItem *item4=new QTreeWidgetItem();
item4->setText(0,"中华田园猫");
item1->addChild(item4);

3.2 节点的动态增删逻辑

树形控件的操作逻辑比表格稍微复杂,因为它涉及到节点之间关系的判断。

添加顶层节点:

直接读取输入框文本,创建Item,并调用addTopLevelItem

cpp 复制代码
void Widget::on_pushButton_insertTopLevelitem_clicked()
{
    //先获取到输入框中的内容
    const QString&text=ui->lineEdit->text();
    //构造一个QTreeWidgetItem对象
    QTreeWidgetItem* item=new QTreeWidgetItem();
    item->setText(0,text);
    //添加到顶层节点中
    ui->treeWidget->addTopLevelItem(item);
}

添加子节点:

必须先确定父节点。通过ui->treeWidget->currentItem()获取当前选中的节点。如果未选中任何节点,则操作直接返回。选中节点后,新创建的Item通过addChild方法挂载到当前节点下,使其成为当前节点的子元素。

cpp 复制代码
void Widget::on_pushButton_insertItem_clicked()
{
    //获取到当前选中的节点
    QTreeWidgetItem *currentItem=ui->treeWidget->currentItem();
    if(currentItem==nullptr)
    {
        return  ;
    }
    //先获取到输入框中的内容
    const QString&text=ui->lineEdit->text();
    //构造一个QTreeWidgetItem对象
    QTreeWidgetItem* item=new QTreeWidgetItem();
    item->setText(0,text);
    //插入到选中节点的子节点

    //currentItem是父节点,我们需要将新创建的子节点变成这个currentItem节点的儿子
    currentItem->addChild(item);
}

删除节点:

删除操作需要区分顶层节点和普通子节点,因为移除的API调用不同。

  1. 获取当前选中节点。
  2. 尝试获取该节点的父节点(parent())。
  3. 如果父节点为nullptr,说明选中的是顶层节点。此时需要先找到该节点在顶层列表中的索引(indexOfTopLevelItem),然后使用takeTopLevelItem将其从视图中移除。
  4. 如果父节点存在,说明是子节点。直接调用父节点的removeChild方法移除该子节点。
cpp 复制代码
void Widget::on_pushButton_deleteItem_clicked()
{
    //获取到当前选中的节点
    QTreeWidgetItem *currentItem=ui->treeWidget->currentItem();
    if(currentItem==nullptr)
    {
        return  ;
    }
    //删除选中的元素,先要获取到父元素,通过父元素进行删除操作
    QTreeWidgetItem*parent=currentItem->parent();
    if(parent==nullptr)
    {
        //顶层元素
        ui->treeWidget->indexOfTopLevelItem(currentItem);//看看自己是第几个元素,下标
        ui->treeWidget->takeTopLevelItem(ui->treeWidget->indexOfTopLevelItem(currentItem));//删除这个下标的元素
    }
    else
    {
        //普通元素
        parent->removeChild(currentItem);
    }  
}

四、QTabWidget:多视图切换与标签页管理

QTabWidget提供了一种堆叠式的视图管理方案。通过顶部的标签栏,用户可以在不同的页面(Widget)之间快速切换,而在同一时刻,只有一个页面是可见的。这种设计极大地节省了屏幕空间,适合功能模块较多的应用程序。

在Qt Designer中,可以直接向Tab Widget中拖拽控件。下图展示了在两个默认标签页中分别放置QLabel及其显示内容。

设计器也支持通过右键菜单直接增加新的标签页,这对应着insertTabaddTab的静态操作。

在运行态,标签页的切换流畅且直观。

QTabWidget拥有丰富的属性,例如可以设置标签的位置(上下左右)、形状以及是否可关闭等。


4.1 标签页的动态管理

在实际应用中,标签页往往需要根据用户操作动态生成或关闭。代码示例演示了如何通过按钮来增加和删除标签页。

初始化:

首先对默认存在的两个标签页进行内容填充。可以通过ui->tabui->tab_2直接访问设计器中生成的页面对象,并向其指定父对象来添加子控件。

cpp 复制代码
//先在每个标签页中添加一个 label
QLabel*label1=new QLabel(ui->tab);
label1->setText("标签页1");
label1->resize(100,50);//设置尺寸
QLabel*label2=new QLabel(ui->tab_2);
label2->setText("标签页2");
label2->resize(100,50);

动态添加标签页:
addTab函数是核心。它接受两个参数:一个是页面容器(通常是QWidget或其子类),另一个是标签显示的文本。

为了增强交互体验,添加新标签页后,通常希望视图自动跳转到新页面。这可以通过setCurrentIndex实现,索引值为当前总页数减一(或添加前的总页数)。

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    //使用addTab来创建新的标签页
    //参数一 要指定一个QWidget
    //参数二 指定这个标签页的text(标题 ),次粗的标题就叫做Tab+数字
    int  count=ui->tabWidget->count();//获取到标签页的数量
    QWidget*w =new QWidget();
    ui->tabWidget->addTab(w,QString("Tab ")+QString::number(count+1));

    //添加一个QLbale,显示内容
    QLabel*label=new QLabel();
    label->setText(QString("标签页 ")+QString::number(count+1));
    label->resize(100,50);
    //注意:这里需要将label放入新创建的widget w中,或者设置label的父对象为w,
    //虽然原代码片段未显式展示布局设置,但在实际开发中应使用布局管理器或指定父对象
    label->setParent(w); 

    //设置新增的标签页被选中
    ui->tabWidget->setCurrentIndex(count);
}

动态删除标签页:

删除操作依赖于当前选中的索引,通过currentIndex()获取,再调用removeTab()移除。

cpp 复制代码
void Widget::on_pushButton_2_clicked()
{
    //获取到当前选中标签页的下标
    int index=ui->tabWidget->currentIndex();

    //删除标签页
    ui->tabWidget->removeTab(index);
}

4.2 事件响应与信号槽

感知标签页的切换是实现复杂逻辑的关键。QTabWidget提供了currentChanged(int index)信号,每当用户切换标签页时,该信号就会触发,并传递当前页面的索引。

在槽函数中,可以根据索引执行特定的逻辑,例如刷新数据、暂停视频播放或记录日志。

cpp 复制代码
void Widget::on_tabWidget_currentChanged(int index)
{
    qDebug()<<"当前选中的标签页是"<<index;
}

运行结果显示,每当点击不同的标签,控制台都会准确输出对应的索引值。

总结

QTableWidgetQTreeWidgetQTabWidget构成了Qt界面开发中处理结构化数据和多视图导航的基石。它们虽然形态各异,但在API设计上遵循了Qt一贯的逻辑:基于索引(Index)的访问、基于项(Item)的数据封装以及基于信号槽(Signal & Slot)的事件驱动。熟练掌握这些控件的初始化、动态增删及事件处理,能够显著提升应用程序的数据展示能力和用户交互体验。同时,合理利用QGroupBox等容器控件,可以进一步提升界面的条理性和可用性。

相关推荐
冠希陈、3 小时前
PHP 判断是否是移动端,更新鸿蒙系统
android·开发语言·php
春生野草3 小时前
Redis
数据库·redis·缓存
HDO清风3 小时前
CASIA-HWDB2.x 数据集DGRL文件解析(python)
开发语言·人工智能·pytorch·python·目标检测·计算机视觉·restful
2201_756989093 小时前
C++中的事件驱动编程
开发语言·c++·算法
weixin_499771554 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
weixin_452159554 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
策知道4 小时前
依托政府工作报告准备省考【经验贴】
大数据·数据库·人工智能·搜索引擎·政务
多米Domi0114 小时前
0x3f 第48天 面向实习的八股背诵第五天 + 堆一题 背了JUC的题,java.util.Concurrency
开发语言·数据结构·python·算法·leetcode·面试
2301_822377654 小时前
模板元编程调试方法
开发语言·c++·算法