QT - QT开发进阶合集
基础知识
此合集是对c++有基础知识同学,可以快速对QT进行理解。每个小章节,都会有对应的代码,进行示例。以下所有项目使用的QT版本为6.9.1,所谓一通百通,大家不用纠结与某个版本的问题,版本大体都兼容,只有某些函数发生了改变而已。
1. 信号与槽
信号:
对象发出的事件,槽函数:
对象发出的事件后,要执行的函数connect:
连接信号与槽函数的方式,五个参数connect参数说明
参数一:信号源,值发送信号方
参数二:信号源发送的信号,即button的动作,这里是点击后这个事件发生的动作
参数三:信号的接收方,即接收信号的对象
参数四:接收信号的对象的槽函数,即要事件发生后执行的函数,
参数五:额外的参数(一般不写),通常是指连接类型,即信号和槽的连接方式
类型:
Qt::AutoConnection:自动连接,默认值,通常在主线程中使用
Qt::DirectConnection:直接连接,信号和槽在同一线程中执行
Qt::QueuedConnection:队列连接,信号和槽在不同线程中执行
Qt::BlockingQueuedConnection:阻塞队列连接,信号和槽在不同线程中执行,但会阻塞发送信号的线程,直到槽函数执行完毕
Qt::UniqueConnection:唯一连接,确保信号和槽只连接一次
Qt::ConnectionType:连接类型,指定信号和槽的连接方式
原因:
线程安全:在多线程环境下,选择合适的连接类型非常重要,以避免死锁或崩溃
性能:不同的连接类型对性能有不同的影响,选择合适的连接类型可以优化程序的性能
dialog.h
#ifndef DIALOG_H
#define DIALOG_H#include <QDialog>
#include <qlabel.h>
#include <qpushbutton.h>
#include <QLineEdit>class Dialog : public QDialog
{Q_OBJECTpublic:Dialog(QWidget *parent = nullptr);~Dialog();private:QLabel *lab1, *lab2;QLineEdit *edit;QPushButton *btn;private:void AreaCal(int x); // 计算面积
};
#endif // DIALOG_H
dialog.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));
}
2. 七类实用控件
在此,因为没有时间把所有的写完整,这次,只写了六个,但是其他控件的原理与规则,与这个其实差不多少,这个模块其实更在意的是槽与函数的使用,即怎么写槽函数,怎么连接槽函数,知道每个空间的信号。其实,信号应该是可以自定义的。我暂时不知道。后续会给出答案。
1. ComboBox & FontComboBox
ComboBox:或者理解为下拉框选择器
FontCOmboBox:即字体样式选择器,我没有写代码,因为原理一样
1.1 拖控件
1.2 代码实现
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头文件代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>// 添加控件QComBoBox
#include <QComboBox>QT_BEGIN_NAMESPACE
namespace Ui
{class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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代码
#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头文件代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QLineEdit>
#include <QTextEdit>QT_BEGIN_NAMESPACE
namespace Ui
{class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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
3. 高级控件精进
1. Tree View
简而言之,是一种数据与控件分离的架构,不同于treeWidget直接有内置可以直接操作,他需要引入相关的模型。
1. treeView树形控件原理分析
QTreeView 的 Model-View 架构原理
1. 分离关注点的设计模式
// 数据模型 - 负责数据存储和管理standardItemModel = new QStandardItemModel(ui->treeView);// 视图控件 - 负责数据显示和用户交互ui->treeView->setModel(standardItemModel);
这是 Qt 的 Model-View 架构,将数据(Model)和显示(View)完全分离:
- Model:管理数据的存储、结构、增删改查
- View:负责数据的可视化呈现和用户交互
2. 为什么要用 QStandardItemModel?
TreeView 本身只是一个"空壳"显示控件,它不存储任何数据。所有的数据都存储在 Model 中:
- QTreeView:只负责绘制界面、处理用户点击、滚动等UI交互
- QStandardItemModel:存储树形结构的实际数据、节点关系、节点属性等
3. 这样设计的优势
数据与显示解耦:
- 同一份数据可以用不同的 View 显示(TreeView、ListView、TableView)
- 数据变化时,View 会自动更新显示
内存效率:
- View 只渲染可见的部分,不会为所有数据创建UI对象
- 大数据量时性能更好
灵活性:
- 可以轻松切换不同的显示方式
- 可以自定义 Model 实现复杂的数据逻辑
2. 实现
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销毁时会自动销毁modelstandardItemModel = 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头文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QStandardItemModel> // 数据模型的类
#include <QMessageBox> // 消息框
#include <QInputDialog> // 输入对话框
#include <QModelIndex> // 模型索引
#include <functional> // 用于std::functionQT_BEGIN_NAMESPACE
namespace Ui
{class MainWindow;
}
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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代码(部分)
exampleWidget = new example(this);// 实例化控件对象treeWidget = new QTreeWidget(this);// 设置框与页面大小this->setGeometry(100, 200, 1000, 800);treeWidget->setGeometry(200, 150, 600, 500);// 添加头标签treeWidget->setHeaderLabel("Tree Widget 示例");// 设置树形控件的根节点-根节点的父节点为treeWidgetQTreeWidgetItem *item1 = new QTreeWidgetItem(treeWidget);item1->setText(0, "根节点 1");// 添加子节点,子节点的父节点为item1QTreeWidgetItem *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代码:
#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;
}
4. View 与 Widget
核心区别对比表
特性 View类(Model/View) Widget类(Item-Based) 数据管理 通过Model管理 直接管理Item对象 灵活性 高度灵活 简单直观 性能 大数据量时更好 小数据量时足够 学习曲线 较陡峭 平缓 自定义能力 强大 有限
在此根据treeView与treeWidget的区别,直接引出所有的View与Widget的区别,
- View类:基于Model/View架构,适合复杂应用
- Widget类:基于Item操作,适合简单应用
目前的话,treeView 与 ListView 其实是一样的设计模式,或者说,其他的也是一样的设计模式,在此,我再过多的叙述,大家,可以根据上方的代码,进行之后的代码的设计。
5. QMessageBox
不必过多赘述
进阶知识
1. QDockWidget
停靠窗口,页面可以包容各种插件,例如TextEdit,或者日历什么的
Dock停靠页面 2025-08-15 10-01-50.mp4
2. QStackedWidget
堆栈窗口
3. QSplitter
分割窗口