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等容器控件,可以进一步提升界面的条理性和可用性。

相关推荐
枕书10 小时前
Oracle 19c RAC 双机高可用底座部署手册(PVE 架构版)
数据库·oracle·pve
单片机学习之路11 小时前
【Python】输入input函数
开发语言·python
一个有温度的技术博主11 小时前
Redis RDB持久化原理:一次快照背后的“分身术”与“读心术”
数据库·redis·缓存
小孤月11 小时前
关系型数据库:(eg:mysql)支持事务 ACID 特性
数据库
cch891811 小时前
ThinkPHP6.x全面升级:性能与功能双飞跃
开发语言·vue.js·后端·golang
yangyanping2010811 小时前
Go语言学习之Go Gin 生产级 flag 启动命令模板
开发语言·学习·golang
辰风沐阳11 小时前
MySQL 联合索引
数据库·mysql
冉佳驹11 小时前
Qt【第七篇】 ——— QSS 样式表与绘图 API 核心用法及 UI 定制功能总结
qt·qbrush·qpainter·qss·paintevent·qpen
xyq202411 小时前
R语言处理JSON文件的方法详解
开发语言
Yvonne爱编码11 小时前
数据库---Day7 数据表设计
数据库·oracle