QT - QT开发进阶合集

基础知识

此合集是对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 架构原理

  1. 分离关注点的设计模式
cpp 复制代码
// 数据模型 - 负责数据存储和管理

standardItemModel = new QStandardItemModel(ui->treeView);

// 视图控件 - 负责数据显示和用户交互

ui->treeView->setModel(standardItemModel);

这是 Qt 的 Model-View 架构,将数据(Model)和显示(View)完全分离:

  • Model:管理数据的存储、结构、增删改查
  • View:负责数据的可视化呈现和用户交互
  1. 为什么要用 QStandardItemModel?

TreeView 本身只是一个"空壳"显示控件,它不存储任何数据。所有的数据都存储在 Model 中:

  • QTreeView:只负责绘制界面、处理用户点击、滚动等UI交互
  • QStandardItemModel:存储树形结构的实际数据、节点关系、节点属性等
  1. 这样设计的优势

  2. 数据与显示解耦

    • 同一份数据可以用不同的 View 显示(TreeView、ListView、TableView)
    • 数据变化时,View 会自动更新显示
  3. 内存效率

    • View 只渲染可见的部分,不会为所有数据创建UI对象
    • 大数据量时性能更好
  4. 灵活性

    • 可以轻松切换不同的显示方式
    • 可以自定义 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生成,很值得学习的项目

链接:Qt--- 布局综合项目(Splitter,Stacked,Dock)-CSDN博客

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

分割窗口(分裂器布局)

  1. horizontal 水平分裂器

  2. vertical 垂直分裂器

二. qml动画特效

qml(描述性的脚本语言)语法格式像css,部分支持JavaScript的编程控制,qml是Qt Quick的核心组件,在移动电话,媒体播放器,机顶盒,有比较重要的作用。

  1. PropertyAnimation元素 (动画元素)

  2. Qt Creator 对QML完美支持,Qt Quick设计器,QML与c++混合编程等等

1. Qml项目的创建

三. 数据可视化(QWidget)

Qt 3D数据可视化控制面板-CSDN直播

以上链接为3d数据可视化的控制面板的小项目,由chat Gpt5完成的。

四. 正则表达式

本章节,其实不用过多叙述,知道大概怎么个事就行。

五. 线程

1. 基础知识

作用
  1. 提高性能(并发执行),资源利用(内存),处理复杂逻辑,增强用户体验,网络通信应用(线程间的通信)
使用的条件
  1. 耗时多

  2. 各种任务相对独立

  3. 实时系统应用

  4. 任务的优先级

知识点

一个进程中的所有线程共享父进程的变量,但是同时每个线程都可以拥有自己的变量

2. 常用API

1. void *ThreadPro(void *args);

Qt编程中,线程函数不可以直接调用,需要实现虚拟函数 QThread::run();,线程的建立,会生成唯一的线程号,线程结束之后,自动消失。

  • Pthread creat (创建线程)
  • Pthread exit (线程终止自身的执行)
  • Pthread join (等待一个线程结束)
  • Pthread self (获取线程id)
  • Pthread cancel(取消另一个线程)
  • Pthread kill (向线程发送一个信号)

项目

相关推荐
Quz3 天前
QML Hello World 入门示例
qt
xcyxiner6 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner7 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner7 天前
DicomViewer (添加模型类)3
qt
xcyxiner8 天前
DicomViewer (目录调整) 2
qt
xcyxiner8 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00610 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术10 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园10 天前
C++20 Modules 模块详解
java·开发语言·spring
swordbob10 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio