基础知识
此合集是对c++有基础知识同学,可以快速对QT进行理解。每个小章节,都会有对应的代码,进行示例。以下所有项目使用的QT版本为6.9.1,所谓一通百通,大家不用纠结与某个版本的问题,版本大体都兼容,只有某些函数发生了改变而已。
一. 信号与槽
信号:
对象发出的事件,
槽函数:
对象发出的事件后,要执行的函数
connect:
连接信号与槽函数的方式,五个参数
connect参数说明
参数一:信号源,值发送信号方
参数二:信号源发送的信号,即button的动作,这里是点击后这个事件发生的动作
参数三:信号的接收方,即接收信号的对象
参数四:接收信号的对象的槽函数,即要事件发生后执行的函数,
参数五:额外的参数(一般不写),通常是指连接类型,即信号和槽的连接方式
类型:
Qt::AutoConnection:自动连接,默认值,通常在主线程中使用
Qt::DirectConnection:直接连接,信号和槽在同一线程中执行
Qt::QueuedConnection:队列连接,信号和槽在不同线程中执行
Qt::BlockingQueuedConnection:阻塞队列连接,信号和槽在不同线程中执行,但会阻塞发送信号的线程,直到槽函数执行完毕
Qt::UniqueConnection:唯一连接,确保信号和槽只连接一次
Qt::ConnectionType:连接类型,指定信号和槽的连接方式
原因:
线程安全:在多线程环境下,选择合适的连接类型非常重要,以避免死锁或崩溃
性能:不同的连接类型对性能有不同的影响,选择合适的连接类型可以优化程序的性能
dialog.h
cpp
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <qlabel.h>
#include <qpushbutton.h>
#include <QLineEdit>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
private:
QLabel *lab1, *lab2;
QLineEdit *edit;
QPushButton *btn;
private:
void AreaCal(int x); // 计算面积
};
#endif // DIALOG_H
dialog.cpp
cpp
#include "dialog.h"
#include <QGridLayout> //布局的头文件
const static double PI = 3.14;
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
// 实例化对应的按钮标签, 编辑框
// lab1 = new QLabel("请输入圆半径",this);
// 提示标签
lab1 = new QLabel(this);
lab1->setText("请输入圆半径");
// 输入框,获取框内的值
edit = new QLineEdit(this);
// 动作按钮
btn = new QPushButton(this);
btn->setText("计算");
/*
connect参数说明
参数一:信号源,值发送信号方
参数二:信号源发送的信号,即button的动作,这里是点击后这个事件发生的动作
参数三:信号的接收方,即接收信号的对象
参数四:接收信号的对象的槽函数,即要事件发生后执行的函数,
参数五:额外的参数(一般不写),通常是指连接类型,即信号和槽的连接方式
类型:
Qt::AutoConnection:自动连接,默认值,通常在主线程中使用
Qt::DirectConnection:直接连接,信号和槽在同一线程中执行
Qt::QueuedConnection:队列连接,信号和槽在不同线程中执行
Qt::BlockingQueuedConnection:阻塞队列连接,信号和槽在不同线程中执行,但会阻塞发送信号的线程,直到槽函数执行完毕
Qt::UniqueConnection:唯一连接,确保信号和槽只连接一次
Qt::ConnectionType:连接类型,指定信号和槽的连接方式
原因:
线程安全:在多线程环境下,选择合适的连接类型非常重要,以避免死锁或崩溃
性能:不同的连接类型对性能有不同的影响,选择合适的连接类型可以优化程序的性能
*/
connect(btn, &QPushButton::clicked, this, [this]()
{
QString inputText = edit->text();
int x = inputText.toInt();
AreaCal(x); },Qt::AutoConnection );
// 显示面积标签
lab2 = new QLabel(this);
// 布局调整
QGridLayout *m = new QGridLayout(this);
m->addWidget(lab1, 0, 0);
m->addWidget(edit, 20, 0);
m->addWidget(btn, 40, 0);
m->addWidget(lab2, 60, 0);
}
Dialog::~Dialog() {}
void Dialog::AreaCal(int x)
{
// 圆的面积 --- Π*r*r;
double area = PI * x * x;
lab2->setText("面积为:" + QString::number(area));
}
二. 七类实用控件
在此,因为没有时间把所有的写完整,这次,只写了六个,但是其他控件的原理与规则,与这个其实差不多少,这个模块其实更在意的是槽与函数的使用,即怎么写槽函数,怎么连接槽函数,知道每个空间的信号。其实,信号应该是可以自定义的。我暂时不知道。后续会给出答案。
1. ComboBox & FontComboBox
ComboBox:或者理解为下拉框选择器
FontCOmboBox:即字体样式选择器,我没有写代码,因为原理一样
1.1 拖控件

1.2 代码实现
cpp代码
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 主窗口的宽高
this->setGeometry(200, 100, 1000, 800);
// 实例化
cmb = new QComboBox(this);
cmb->setGeometry(50, 50, 150, 50);
// 添加到队列的方式共有两种,
// 1.addItem 在ComboBox的末尾添加一个新选项。
// 2.insertItem 在ComboBox的指定索引位置插入一个新选项。
cmb->insertItem(0, "小学");
cmb->insertItem(1, "初中");
cmb->insertItem(2, "高中");
cmb->insertItem(3, "专科");
cmb->insertItem(4, "本科");
cmb->insertItem(5, "硕士");
cmb->insertItem(6, "博士");
// 设置当前索引
cmb->setCurrentIndex(0);
// 连接信号和槽
connect(cmb, SIGNAL(currentIndexChanged(int)), this, SLOT(cmbIndexChange(int)));
}
MainWindow::~MainWindow()
{
delete ui;
}
// 再此的问题是,如何知道了索引发生改变
void MainWindow::cmbIndexChange(int)
{
// 控制台输出内容
qDebug() << "当前索引发生改变,索引为:" << cmb->currentIndex()
<< ", 当前选项为:" << cmb->currentText();
// 弹框
QMessageBox::information(this, "提示", QString("当前索引发生改变,索引为:%1, 当前选项为:%2").arg(cmb->currentIndex()).arg(cmb->currentText()));
}
h头文件代码
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
// 添加控件QComBoBox
#include <QComboBox>
QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
// 1.声明QComboBox对象
QComboBox *cmb;
public slots:
// QComboBox对象的槽函数
// combox, 有内置发生改变的信号
void cmbIndexChange(int);
};
#endif // MAINWINDOW_H
效果展示:

2. LineEdit & TextEdit
LineEdit:行文本编辑
TextEdit:可以理解为块文本编辑
1.1 拖控件

1.2 代码实现
cpp代码
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 参数:设置窗口的左上角位置,参数2:设置窗口的宽度,参数3:设置窗口的高度
this->setGeometry(200, 100, 1000, 800);
lineEdit = new QLineEdit(this);
lineEdit->setGeometry(50, 50, 120, 40);
connect(lineEdit, SIGNAL(editingFinished()), this, SLOT(onLineEditEditingFinished()));
textEdit = new QTextEdit(this);
textEdit->setGeometry(50, 200, 120, 80);
// QTextEdit没有editingFinished信号,使用textChanged信号代替
connect(textEdit, SIGNAL(textChanged()), this, SLOT(onTextEditEditingFinished()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onLineEditEditingFinished()
{
// 获取lineEdit的文本内容
QString text = lineEdit->text();
// 在textEdit中显示lineEdit的文本内容
textEdit->setText(text);
}
void MainWindow::onTextEditEditingFinished()
{
// 获取textEdit的文本内容
QString text = textEdit->toPlainText();
// 在lineEdit中显示textEdit的文本内容
lineEdit->setText(text);
}
h头文件代码
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLineEdit>
#include <QTextEdit>
QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QLineEdit *lineEdit;
QTextEdit *textEdit;
private slots:
// lineEdit编辑完成
void onLineEditEditingFinished();
// textEdit编辑完成
void onTextEditEditingFinished();
};
#endif // MAINWINDOW_H
效果展示:
上方的为行编辑,即输入完,点击enter键,可以使得textEdit获得同步,信号为editfinished
下方的为文本编辑块,输入完,可以使得上方的lineEdit同步,信号为textchanged

三. 高级控件精进
1. Tree View
简而言之,是一种数据与控件分离的架构,不同于treeWidget直接有内置可以直接操作,他需要引入相关的模型。
1. treeView树形控件原理分析
QTreeView 的 Model-View 架构原理
- 分离关注点的设计模式
cpp// 数据模型 - 负责数据存储和管理 standardItemModel = new QStandardItemModel(ui->treeView); // 视图控件 - 负责数据显示和用户交互 ui->treeView->setModel(standardItemModel);
这是 Qt 的 Model-View 架构,将数据(Model)和显示(View)完全分离:
- Model:管理数据的存储、结构、增删改查
- View:负责数据的可视化呈现和用户交互
- 为什么要用 QStandardItemModel?
TreeView 本身只是一个"空壳"显示控件,它不存储任何数据。所有的数据都存储在 Model 中:
- QTreeView:只负责绘制界面、处理用户点击、滚动等UI交互
- QStandardItemModel:存储树形结构的实际数据、节点关系、节点属性等
这样设计的优势
数据与显示解耦:
- 同一份数据可以用不同的 View 显示(TreeView、ListView、TableView)
- 数据变化时,View 会自动更新显示
内存效率:
- View 只渲染可见的部分,不会为所有数据创建UI对象
- 大数据量时性能更好
灵活性:
- 可以轻松切换不同的显示方式
- 可以自定义 Model 实现复杂的数据逻辑
2. 实现

cpp文件
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// ==================== 初始化TreeView数据模型 ====================
// 1. 创建标准数据模型对象
// QStandardItemModel是Qt提供的标准模型类,用于管理树形/表格数据
// 参数ui->treeView指定父对象,这样当treeView销毁时会自动销毁model
standardItemModel = new QStandardItemModel(ui->treeView);
// 2. 将数据模型绑定到TreeView控件
// Model-View架构的核心:将数据(Model)与显示(View)分离
// setModel()建立了Model和View之间的连接,数据变化时View会自动更新
ui->treeView->setModel(standardItemModel);
// 3. 设置表头列的自动调整模式
// QHeaderView::Stretch:列宽自动拉伸填满整个控件宽度
// 这样无论窗口如何调整大小,列都会自动适应宽度
ui->treeView->header()->setSectionResizeMode(QHeaderView::Stretch);
// 4. 设置表头标题
// QStringList创建字符串列表,这里只有一列所以只有一个标题
// 如果是多列树形控件,可以添加多个标题:QStringList() << "名称" << "类型" << "大小"
standardItemModel->setHorizontalHeaderLabels(QStringList() << "节点名称");
// ==================== 优化TreeView显示效果 ====================
// 5. 设置选择行为:选择整行而不是单个单元格
ui->treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
// 6. 设置选择模式:单选模式(一次只能选择一行)
ui->treeView->setSelectionMode(QAbstractItemView::SingleSelection);
// 7. 启用右键菜单(如果需要的话)
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_insert_top_btn_clicked()
{
// ==================== 添加顶级节点(一级节点)====================
// 1. 获取当前模型中已有的顶级节点数量
// rowCount()返回顶级节点的数量,用于生成新节点的序号
int index = standardItemModel->rowCount();
// 2. 创建QList容器来存储一行的数据项
// 虽然这里只有一列,但QStandardItemModel的appendRow()需要QList参数
// 这样设计是为了支持多列的情况(如:名称、类型、大小等多列)
QList<QStandardItem *> toplist;
// 3. 创建新的标准数据项
// QStandardItem是模型中的基本数据单元,可以存储文本、图标、用户数据等
// QString::arg()是字符串格式化方法,%1会被替换为(index + 1)的值
// 例如:当index=0时,显示"一级节点:1-1"
toplist << new QStandardItem(QString("一级节点:%1-1").arg(index + 1));
// 4. 为节点设置自定义数据(用于标识节点类型)
// setData()可以存储额外的数据,这里存储-1表示这是顶级节点
// Qt::UserRole + 1 是自定义角色,Qt预留了UserRole之后的角色供用户使用
// 这样我们可以通过data(Qt::UserRole + 1)来获取节点类型
toplist[0]->setData(-1, Qt::UserRole + 1); // -1表示顶级节点类型
// 5. 设置节点图标(可选)
// 可以为不同类型的节点设置不同图标,增强视觉效果
// toplist[0]->setIcon(QIcon(":/icons/folder.png"));
// 6. 设置节点为可编辑(可选)
// 默认情况下节点是可编辑的,用户可以双击修改节点名称
toplist[0]->setEditable(true);
// 7. 将新创建的行添加到数据模型的根节点下
// appendRow()会自动触发视图更新,新节点会立即显示在TreeView中
// 这是Model-View架构的优势:数据变化时视图自动同步
standardItemModel->appendRow(toplist);
// 8. 可选:自动选中新添加的节点
QModelIndex newIndex = standardItemModel->index(index, 0);
ui->treeView->setCurrentIndex(newIndex);
}
void MainWindow::on_insert_child_btn_clicked()
{
// ==================== 添加子节点(二级节点)====================
// 1. 获取当前选中的节点索引
// selectionModel()返回选择模型,管理TreeView中的选择状态
// currentIndex()获取当前选中项的模型索引
QModelIndex currentIndex = ui->treeView->selectionModel()->currentIndex();
// 2. 检查是否有选中的节点
if (!currentIndex.isValid())
{
// 如果没有选中任何节点,显示提示消息
QMessageBox::warning(this, "警告", "请先选择一个父节点!");
return;
}
// 3. 通过模型索引获取对应的标准数据项
// itemFromIndex()将QModelIndex转换为QStandardItem指针
// 这样我们就可以直接操作数据项,添加子节点
QStandardItem *parentItem = standardItemModel->itemFromIndex(currentIndex);
// 4. 检查父节点是否有效
if (!parentItem)
{
QMessageBox::warning(this, "错误", "无法获取父节点!");
return;
}
// 5. 获取父节点已有的子节点数量
// rowCount()返回该节点下子节点的数量,用于生成子节点序号
int childCount = parentItem->rowCount();
// 6. 创建子节点
QList<QStandardItem *> childList;
// 7. 生成子节点名称
// 格式:父节点名称 + "-子节点" + 序号
QString parentText = parentItem->text();
QString childText = QString("%1-子节点%2").arg(parentText).arg(childCount + 1);
childList << new QStandardItem(childText);
// 8. 为子节点设置类型标识
// 这里用父节点在顶级的位置作为类型标识
// 例如:第一个顶级节点的子节点类型为0,第二个为1,以此类推
int parentRow = currentIndex.row();
childList[0]->setData(parentRow, Qt::UserRole + 1); // 存储父节点的行号作为类型
// 9. 设置子节点属性
childList[0]->setEditable(true); // 可编辑
// childList[0]->setIcon(QIcon(":/icons/file.png")); // 可设置不同图标
// 10. 将子节点添加到父节点下
// appendRow()添加到指定父节点下,而不是根节点
parentItem->appendRow(childList);
// 11. 展开父节点以显示新添加的子节点
// expand()展开指定的节点,让用户能看到新添加的子节点
ui->treeView->expand(currentIndex);
// 12. 选中新添加的子节点
QModelIndex newChildIndex = standardItemModel->index(childCount, 0, currentIndex);
ui->treeView->setCurrentIndex(newChildIndex);
}
void MainWindow::on_delete_btn_clicked()
{
// ==================== 删除选中的节点 ====================
// 1. 获取当前选中的节点索引
QModelIndex currentIndex = ui->treeView->selectionModel()->currentIndex();
// 2. 检查是否有选中的节点
if (!currentIndex.isValid())
{
QMessageBox::warning(this, "警告", "请先选择要删除的节点!");
return;
}
// 3. 获取选中节点的数据项
QStandardItem *selectedItem = standardItemModel->itemFromIndex(currentIndex);
if (!selectedItem)
{
QMessageBox::warning(this, "错误", "无法获取选中的节点!");
return;
}
// 4. 确认删除操作
// 获取节点文本用于确认对话框
QString itemText = selectedItem->text();
// 检查是否有子节点
int childCount = selectedItem->rowCount();
QString message;
if (childCount > 0)
{
message = QString("确定要删除节点 \"%1\" 及其所有 %2 个子节点吗?").arg(itemText).arg(childCount);
}
else
{
message = QString("确定要删除节点 \"%1\" 吗?").arg(itemText);
}
// 5. 显示确认对话框
QMessageBox::StandardButton reply = QMessageBox::question(
this,
"确认删除",
message,
QMessageBox::Yes | QMessageBox::No, // 按钮选项
QMessageBox::No // 默认按钮
);
// 6. 如果用户确认删除
if (reply == QMessageBox::Yes)
{
// 7. 执行删除操作
// 获取父节点
QStandardItem *parentItem = selectedItem->parent();
if (parentItem)
{
// 如果有父节点,说明这是子节点
// 从父节点中移除该行
int row = selectedItem->row();
parentItem->removeRow(row);
}
else
{
// 如果没有父节点,说明这是顶级节点
// 从模型根节点中移除该行
int row = currentIndex.row();
standardItemModel->removeRow(row);
}
// 8. 删除成功提示(可选)
// QMessageBox::information(this, "成功", "节点删除成功!");
}
}
void MainWindow::on_get_btn_clicked()
{
// ==================== 获取并显示选中节点的详细信息 ====================
// 1. 获取当前选中的节点索引
QModelIndex currentIndex = ui->treeView->selectionModel()->currentIndex();
// 2. 检查是否有选中的节点
if (!currentIndex.isValid())
{
QMessageBox::warning(this, "警告", "请先选择一个节点!");
return;
}
// 3. 获取选中节点的数据项
QStandardItem *selectedItem = standardItemModel->itemFromIndex(currentIndex);
if (!selectedItem)
{
QMessageBox::warning(this, "错误", "无法获取选中的节点!");
return;
}
// 4. 收集节点的详细信息
QString nodeInfo;
// 基本信息
nodeInfo += "=== 节点详细信息 ===\n\n";
nodeInfo += QString("节点名称: %1\n").arg(selectedItem->text());
nodeInfo += QString("节点行号: %1\n").arg(selectedItem->row());
nodeInfo += QString("节点列号: %1\n").arg(selectedItem->column());
// 层级信息
int level = 0;
QStandardItem *parent = selectedItem->parent();
while (parent)
{
level++;
parent = parent->parent();
}
nodeInfo += QString("节点层级: %1\n").arg(level == 0 ? "顶级节点" : QString("第%1级").arg(level + 1));
// 自定义数据
QVariant userData = selectedItem->data(Qt::UserRole + 1);
if (userData.isValid())
{
nodeInfo += QString("节点类型: %1\n").arg(userData.toString());
}
// 子节点信息
int childCount = selectedItem->rowCount();
nodeInfo += QString("子节点数量: %1\n").arg(childCount);
if (childCount > 0)
{
nodeInfo += "\n子节点列表:\n";
for (int i = 0; i < childCount; ++i)
{
QStandardItem *child = selectedItem->child(i, 0);
if (child)
{
nodeInfo += QString(" - %1\n").arg(child->text());
}
}
}
// 父节点信息
QStandardItem *parentItem = selectedItem->parent();
if (parentItem)
{
nodeInfo += QString("\n父节点: %1\n").arg(parentItem->text());
// 兄弟节点信息
int siblingCount = parentItem->rowCount();
nodeInfo += QString("兄弟节点数量: %1\n").arg(siblingCount - 1); // 减去自己
if (siblingCount > 1)
{
nodeInfo += "\n兄弟节点列表:\n";
for (int i = 0; i < siblingCount; ++i)
{
QStandardItem *sibling = parentItem->child(i, 0);
if (sibling && sibling != selectedItem)
{
nodeInfo += QString(" - %1\n").arg(sibling->text());
}
}
}
}
else
{
nodeInfo += "\n父节点: 无(顶级节点)\n";
// 同级顶级节点信息
int topLevelCount = standardItemModel->rowCount();
nodeInfo += QString("同级节点数量: %1\n").arg(topLevelCount - 1);
if (topLevelCount > 1)
{
nodeInfo += "\n同级节点列表:\n";
for (int i = 0; i < topLevelCount; ++i)
{
QStandardItem *topItem = standardItemModel->item(i, 0);
if (topItem && topItem != selectedItem)
{
nodeInfo += QString(" - %1\n").arg(topItem->text());
}
}
}
}
// 路径信息(从根节点到当前节点的完整路径)
QStringList pathList;
QStandardItem *current = selectedItem;
while (current)
{
pathList.prepend(current->text());
current = current->parent();
}
nodeInfo += QString("\n节点路径: %1\n").arg(pathList.join(" -> "));
// 5. 显示信息对话框
QMessageBox::information(this, "节点信息", nodeInfo);
}
void MainWindow::on_pushButton_5_clicked()
{
// ==================== 展开/折叠所有节点的切换功能 ====================
// 1. 检查是否有节点数据
if (standardItemModel->rowCount() == 0)
{
QMessageBox::information(this, "提示", "当前没有任何节点!");
return;
}
// 2. 检查当前的展开状态
// 我们通过检查第一个顶级节点是否展开来判断当前状态
bool isExpanded = false;
QModelIndex firstIndex = standardItemModel->index(0, 0);
if (firstIndex.isValid())
{
isExpanded = ui->treeView->isExpanded(firstIndex);
}
// 3. 根据当前状态执行相反操作
if (isExpanded)
{
// 如果当前是展开状态,则折叠所有节点
ui->treeView->collapseAll();
// 可选:显示操作提示
// QMessageBox::information(this, "操作完成", "已折叠所有节点!");
}
else
{
// 如果当前是折叠状态,则展开所有节点
ui->treeView->expandAll();
// 可选:显示操作提示
// QMessageBox::information(this, "操作完成", "已展开所有节点!");
}
// 4. 高级功能:也可以实现更复杂的展开逻辑
/*
// 替代方案1:总是展开所有节点
ui->treeView->expandAll();
// 替代方案2:只展开到指定层级(例如只展开前2层)
expandToLevel(2);
// 替代方案3:递归展开有子节点的节点
expandNodesWithChildren();
*/
}
// ==================== 辅助函数实现 ====================
void MainWindow::expandToLevel(int level)
{
// 展开到指定层级的递归函数
// level: 要展开到的层级(0表示只显示顶级节点,1表示展开一层子节点)
if (level < 0)
return;
// 递归函数:展开指定深度
std::function<void(const QModelIndex &, int)> expandRecursively =
[this, &expandRecursively](const QModelIndex &index, int currentLevel)
{
if (currentLevel >= 0)
{
ui->treeView->expand(index);
// 继续展开子节点
int rowCount = standardItemModel->rowCount(index);
for (int i = 0; i < rowCount; ++i)
{
QModelIndex childIndex = standardItemModel->index(i, 0, index);
expandRecursively(childIndex, currentLevel - 1);
}
}
};
// 从根节点开始展开
int topLevelCount = standardItemModel->rowCount();
for (int i = 0; i < topLevelCount; ++i)
{
QModelIndex topIndex = standardItemModel->index(i, 0);
expandRecursively(topIndex, level);
}
}
void MainWindow::expandNodesWithChildren()
{
// 只展开有子节点的节点
std::function<void(const QModelIndex &)> expandIfHasChildren =
[this, &expandIfHasChildren](const QModelIndex &index)
{
int childCount = standardItemModel->rowCount(index);
if (childCount > 0)
{
ui->treeView->expand(index);
// 递归处理子节点
for (int i = 0; i < childCount; ++i)
{
QModelIndex childIndex = standardItemModel->index(i, 0, index);
expandIfHasChildren(childIndex);
}
}
};
// 处理所有顶级节点
int topLevelCount = standardItemModel->rowCount();
for (int i = 0; i < topLevelCount; ++i)
{
QModelIndex topIndex = standardItemModel->index(i, 0);
expandIfHasChildren(topIndex);
}
}
int MainWindow::getNodeLevel(QStandardItem *item)
{
// 获取节点在树中的层级
// 返回值:0表示顶级节点,1表示第二层,以此类推
if (!item)
return -1;
int level = 0;
QStandardItem *parent = item->parent();
while (parent)
{
level++;
parent = parent->parent();
}
return level;
}
h头文件
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QStandardItemModel> // 数据模型的类
#include <QMessageBox> // 消息框
#include <QInputDialog> // 输入对话框
#include <QModelIndex> // 模型索引
#include <functional> // 用于std::function
QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_insert_top_btn_clicked(); // 添加顶级节点
void on_insert_child_btn_clicked(); // 添加子节点
void on_delete_btn_clicked(); // 删除节点
void on_get_btn_clicked(); // 获取节点信息
void on_pushButton_5_clicked(); // 展开/折叠所有节点
private:
Ui::MainWindow *ui;
QStandardItemModel *standardItemModel; // 树形数据模型
// 辅助函数(可选,用于扩展功能)
void expandToLevel(int level); // 展开到指定层级
void expandNodesWithChildren(); // 展开有子节点的节点
int getNodeLevel(QStandardItem *item); // 获取节点层级
};
#endif // MAINWINDOW_H
2. Tree Widget
常规模式
由于TreeWidget内置数据模型,直接调用即可。
使用的方式也比较简单,即反复套即可,

cpp代码(部分)
cpp
exampleWidget = new example(this);
// 实例化控件对象
treeWidget = new QTreeWidget(this);
// 设置框与页面大小
this->setGeometry(100, 200, 1000, 800);
treeWidget->setGeometry(200, 150, 600, 500);
// 添加头标签
treeWidget->setHeaderLabel("Tree Widget 示例");
// 设置树形控件的根节点-根节点的父节点为treeWidget
QTreeWidgetItem *item1 = new QTreeWidgetItem(treeWidget);
item1->setText(0, "根节点 1");
// 添加子节点,子节点的父节点为item1
QTreeWidgetItem *child1 = new QTreeWidgetItem(item1);
child1->setText(0, "子节点 1");
QTreeWidgetItem *child2 = new QTreeWidgetItem(item1);
child2->setText(0, "子节点 2");
// 展开根节点
item1->setExpanded(true);
QTreeWidgetItem *item2 = new QTreeWidgetItem(treeWidget);
item2->setText(0, "根节点 2");
QTreeWidgetItem *child3 = new QTreeWidgetItem(item2);
child3->setText(0, "子节点 3");
QTreeWidgetItem *child4 = new QTreeWidgetItem(item2);
child4->setText(0, "子节点 4");
item2->setExpanded(true);
数据驱动模式(json & 容器数据 & 结构体)
数据驱动,即先写好数据,这个数据可以是个list,也可以是个map,也可以是个map<QString,map<QString,QString>>,若想实现多级,可使用这个多级嵌套,稍微麻烦,
,所以json无疑是一个非常好的选择,或者使用结构体(class)来定义,真正的开发中,肯定是从数据库中拿到对应数值,再展示对应的数据,我更推荐json 与结构体(class)的方式,但是本次使用的是嵌套的集合(c++中叫containner,即容器,无伤大雅,一个意思。)
cpp代码:
cpp
#include "example.h"
#include "ui_example.h"
#include <mainwindow.h>
example::example(QWidget *parent)
: QMainWindow(parent), ui(new Ui::example)
{
ui->setupUi(this);
this->setWindowTitle("使用数据驱动方式");
// 实例化控件对象
treeWidget = new QTreeWidget(this);
// 设置框与页面大小
this->setGeometry(100, 200, 1000, 800);
treeWidget->setGeometry(200, 150, 600, 500);
// 添加头标签
treeWidget->setHeaderLabel("TreeWidget 数据驱动方法");
// 使用数据驱动创建树
createTreeFromData();
}
example::~example()
{
delete ui;
}
void example::on_pushButton_clicked()
{
this->hide();
// 返回到主窗口
MainWindow *mainWindow = qobject_cast<MainWindow *>(this->parent());
if (mainWindow)
{
mainWindow->show();
}
// emit requestClose(); // 发射信号,让父窗口处理,在子类写个信号,主窗口连接这个信号,执行转换窗口的操作。
}
void example::createTreeFromData()
{
// 定义结构化数据 categories是一级分类,items是二级分类
QStringList categories = {"编程语言", "开发工具", "框架库", "数据库"};
// 使用 QMap 存储每个分类下的项目,是二级分类
QMap<QString, QStringList> items = {
{"编程语言", {"C++", "Python", "JavaScript", "Java"}},
{"开发工具", {"Visual Studio", "Qt Creator", "Git", "Docker"}},
{"框架库", {"Qt", "React", "Django", "Spring"}},
{"数据库", {"MySQL", "PostgreSQL", "MongoDB", "Redis"}}};
// 三级分类或者多级分类,可以使用容器嵌套,或者使用结构体定义,或者json数据。
// 批量创建树节点,使用迭代器,便利map容器之中的值
for (const QString &category : categories)
{
// addTreeItem(nullptr, category);这一步是为了创造一级分类且,设置父节点为空,即当前category的数据都是顶级节点
QTreeWidgetItem *categoryItem = addTreeItem(nullptr, category);
// 根据键值对匹配,创建二级分类节点
for (const QString &item : items[category])
{
addTreeItem(categoryItem, item); // categoryItem 作为父节点传入,创建子节点
}
categoryItem->setExpanded(true); // 展开一级分类节点
}
}
QTreeWidgetItem *example::addTreeItem(QTreeWidgetItem *parent, const QString &text)
{
QTreeWidgetItem *item;
if (parent)
{
item = new QTreeWidgetItem(parent);
}
else
{
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, text);
return item;
}
四. View 与 Widget
核心区别对比表
特性 View类(Model/View) Widget类(Item-Based) 数据管理 通过Model管理 直接管理Item对象 灵活性 高度灵活 简单直观 性能 大数据量时更好 小数据量时足够 学习曲线 较陡峭 平缓 自定义能力 强大 有限
在此根据treeView与treeWidget的区别,直接引出所有的View与Widget的区别,
- View类:基于Model/View架构,适合复杂应用
- Widget类:基于Item操作,适合简单应用
目前的话,treeView 与 ListView 其实是一样的设计模式,或者说,其他的也是一样的设计模式,在此,我再过多的叙述,大家,可以根据上方的代码,进行之后的代码的设计。
五. QMessageBox
不必过多赘述
进阶知识
一 布局
三个布局的大项目,我写在了另一个文章,文章是由claude生成,很值得学习的项目
1. QDockWidget
停靠窗口,页面可以包容各种插件,例如TextEdit,或者日历什么的,实际作用,就是可以浮动,停靠在桌面的各种位置。

Dock停靠页面 2025-08-15 10-01-50.mp4
小项目(cpp代码)
cpp
#include "mainwindow.h"
#include <QTextEdit>
#include <QDockWidget>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
/*
结构说明:
MainWindow(QMainWindow)
├── centralWidget(空 QWidget 占位)
├── leftDock(QDockWidget "个人信息表单")
│ └── leftContent(QWidget) ← 必须: QDockWidget 只能接收 QWidget
│ └── leftLayout(QGridLayout) ← 网格: 6 行 2 列 (标签 + 输入框)
└── rightDock(QDockWidget "详细信息")
└── rightContent(QWidget) ← 必须: 作为布局载体
└── rightLayout(QVBoxLayout)
├── avatarLayout(QHBoxLayout) ← 头像选择区域
├── lblIntro(QLabel)
├── editIntro(QTextEdit)
└── btnLayout(QHBoxLayout) ← 按钮行(保存/取消/重置)
说明: 不能把 QLayout 直接交给 QDockWidget, 因为 setWidget 只接受 QWidget。
*/
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 中央占位窗口 (后续可替换为真正业务界面)
QWidget *centerWidget = new QWidget(this);
setCentralWidget(centerWidget);
/************** 左侧 Dock: 表单区域 **************/
QDockWidget *leftDockWidget = new QDockWidget("个人信息表单", this); // 设置标题, 保留可拖动标题栏
leftDockWidget->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
leftDockWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
// 中间内容容器: 必须创建,用来承载布局 (布局不能直接 set 到 dock)
QWidget *leftContent = new QWidget(leftDockWidget); //--父类是dock
QGridLayout *leftLayout = new QGridLayout(leftContent);
leftLayout->setContentsMargins(8, 8, 8, 8);
leftLayout->setHorizontalSpacing(12);
leftLayout->setVerticalSpacing(8);
// 六行表单 (从第0行开始依次往下)
QLabel *lblUserName = new QLabel("用户名:", leftContent);
QLineEdit *editUserName = new QLineEdit(leftContent);
leftLayout->addWidget(lblUserName, 0, 0);
leftLayout->addWidget(editUserName, 0, 1);
QLabel *lblRealName = new QLabel("姓名:", leftContent);
QLineEdit *editRealName = new QLineEdit(leftContent);
leftLayout->addWidget(lblRealName, 1, 0);
leftLayout->addWidget(editRealName, 1, 1);
QLabel *lblAge = new QLabel("年龄:", leftContent);
QLineEdit *editAge = new QLineEdit(leftContent);
leftLayout->addWidget(lblAge, 2, 0);
leftLayout->addWidget(editAge, 2, 1);
QLabel *lblGender = new QLabel("性别:", leftContent);
QLineEdit *editGender = new QLineEdit(leftContent);
leftLayout->addWidget(lblGender, 3, 0);
leftLayout->addWidget(editGender, 3, 1);
QLabel *lblPhone = new QLabel("电话:", leftContent);
QLineEdit *editPhone = new QLineEdit(leftContent);
leftLayout->addWidget(lblPhone, 4, 0);
leftLayout->addWidget(editPhone, 4, 1);
QLabel *lblEmail = new QLabel("邮箱:", leftContent);
QLineEdit *editEmail = new QLineEdit(leftContent);
leftLayout->addWidget(lblEmail, 5, 0);
leftLayout->addWidget(editEmail, 5, 1);
leftDockWidget->setWidget(leftContent); // 把内容容器塞入 dock
addDockWidget(Qt::LeftDockWidgetArea, leftDockWidget);
/************** 右侧 Dock: 详细/简介区域 **************/
QDockWidget *rightDockWidget = new QDockWidget("详细信息", this);
rightDockWidget->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
rightDockWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
QWidget *rightContent = new QWidget(rightDockWidget); // 承载垂直布局
QVBoxLayout *rightLayout = new QVBoxLayout(rightContent);
rightLayout->setContentsMargins(8, 8, 8, 8);
rightLayout->setSpacing(8);
// 头像行 (水平布局1)
QHBoxLayout *avatarLayout = new QHBoxLayout();
QLabel *lblAvatar = new QLabel("头像:", rightContent);
QPushButton *btnSelectAvatar = new QPushButton("选择头像", rightContent);
avatarLayout->addWidget(lblAvatar);
avatarLayout->addWidget(btnSelectAvatar);
avatarLayout->addStretch(); // 推动控件靠左
rightLayout->addLayout(avatarLayout);
// 简介标签
QLabel *lblIntro = new QLabel("个人简介:", rightContent);
rightLayout->addWidget(lblIntro);
// 多行简介编辑框
QTextEdit *editIntro = new QTextEdit(rightContent);
editIntro->setPlaceholderText("请输入个人简介...");
rightLayout->addWidget(editIntro, 1); // 给予拉伸因子让其扩展
// 按钮行 (水平布局2)
QHBoxLayout *btnLayout = new QHBoxLayout();
QPushButton *btnSave = new QPushButton("保存", rightContent);
QPushButton *btnCancel = new QPushButton("取消", rightContent);
QPushButton *btnReset = new QPushButton("重置", rightContent);
btnLayout->addStretch(); // 左侧留空
btnLayout->addWidget(btnSave);
btnLayout->addWidget(btnCancel);
btnLayout->addWidget(btnReset);
rightLayout->addLayout(btnLayout);
rightDockWidget->setWidget(rightContent);
addDockWidget(Qt::RightDockWidgetArea, rightDockWidget);
}
MainWindow::~MainWindow() {}

2. QStackedWidget
堆叠卡片
3. QSplitter
分割窗口(分裂器布局)
horizontal 水平分裂器
vertical 垂直分裂器
二. qml动画特效
qml(描述性的脚本语言)语法格式像css,部分支持JavaScript的编程控制,qml是Qt Quick的核心组件,在移动电话,媒体播放器,机顶盒,有比较重要的作用。
PropertyAnimation元素 (动画元素)
Qt Creator 对QML完美支持,Qt Quick设计器,QML与c++混合编程等等
1. Qml项目的创建




三. 数据可视化(QWidget)
以上链接为3d数据可视化的控制面板的小项目,由chat Gpt5完成的。
四. 正则表达式
本章节,其实不用过多叙述,知道大概怎么个事就行。
五. 线程
1. 基础知识
作用
- 提高性能(并发执行),资源利用(内存),处理复杂逻辑,增强用户体验,网络通信应用(线程间的通信)
使用的条件
耗时多
各种任务相对独立
实时系统应用
任务的优先级
知识点
一个进程中的所有线程共享父进程的变量,但是同时每个线程都可以拥有自己的变量
2. 常用API
1. void *ThreadPro(void *args);
Qt编程中,线程函数不可以直接调用,需要实现虚拟函数 QThread::run();,线程的建立,会生成唯一的线程号,线程结束之后,自动消失。
- Pthread creat (创建线程)
- Pthread exit (线程终止自身的执行)
- Pthread join (等待一个线程结束)
- Pthread self (获取线程id)
- Pthread cancel(取消另一个线程)
- Pthread kill (向线程发送一个信号)