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 (向线程发送一个信号)

项目

相关推荐
anlogic27 分钟前
Java基础 8.18
java·开发语言
沐知全栈开发1 小时前
WebForms XML 文件详解
开发语言
阿巴~阿巴~2 小时前
冒泡排序算法
c语言·开发语言·算法·排序算法
weixin_307779133 小时前
VS Code配置MinGW64编译SQLite3库
开发语言·数据库·c++·vscode·算法
励志不掉头发的内向程序员4 小时前
STL库——string(类函数学习)
开发语言·c++
一百天成为python专家5 小时前
Python循环语句 从入门到精通
开发语言·人工智能·python·opencv·支持向量机·计算机视觉
Sunhen_Qiletian5 小时前
朝花夕拾(五)--------Python 中函数、库及接口的详解
开发语言·python
hqwest5 小时前
C#WPF实战出真汁07--【系统设置】--菜品类型设置
开发语言·c#·wpf·grid设计·stackpanel布局
前路不黑暗@5 小时前
C语言:操作符详解(二)
c语言·开发语言·经验分享·笔记·学习·学习方法·visual studio