上一章节在掌握了信号与槽这一 Qt 核心机制后,接下来就让我们动手落地第一个实战功能 ------ 为菜单"新建项目" 实现业务逻辑。
本节课的实战目标
本次课程我们要实现新建项目的功能,具体如下:
- 点击新建项目,打开一个新建项目的对话框;
- 新建项目对话框中需要用户输入项目名称、选择项目的存放位置,并允许用户进行确定和取消;
- 对新建项目中的标签、输入框、按钮等按照图例进行布局;

为了实现 上面的"新建项目" 这个功能,我们需要引入一个新的、非常常用的界面组件类 ------ QDialog。接下来,我们就将结合信号与槽,一起学习如何使用 QDialog 来创建一个对话框,并完成 "新建项目" 的整个逻辑流程。
QDialog 是什么?
你可以把 QDialog 理解为一个专门用于与用户进行短期交互的窗口。它通常不会像主窗口(QMainWindow)那样承载整个应用的主要功能和菜单工具栏,而是在需要时弹出,完成特定任务后就关闭。
下图是QDialog的继承关系,理解继承关系有助于你掌握 QDialog 的功能来源和在 Qt 控件体系中的位置。

让我们逐层解析:
QObject:
- 位置: 继承链的最顶端。
- 核心功能: QObject 是 Qt 中所有支持信号与槽(Signals & Slots)机制的类的基类。它也提供了对象树管理、事件处理、动态属性等核心功能。
- 对 QDialog 的意义: 正因为继承自 QObject,QDialog 及其子类才能使用信号与槽进行通信,例如,当用户点击对话框上的 "确定" 按钮时,发出一个信号。
QWidget:
- 位置: 直接父类。
- 核心功能: QWidget 是 Qt 中所有用户界面对象(控件)的基类。一个 QWidget 可以理解为一个 "空白画布",它可以显示在屏幕上,接收鼠标、键盘事件,并可以作为其他控件的容器。它提供了窗口的基本特性,如大小、位置、显示 / 隐藏、样式表支持等。
- 对 QDialog 的意义: 继承自 QWidget 意味着 QDialog 本质上是一个可以独立存在的窗口控件。它拥有 QWidget 的所有能力,比如可以设置背景色、可以安装事件过滤器、可以包含其他 QWidget(如 QPushButton, QLabel)作为其子控件。
QDialog:
- 位置: 我们讨论的类本身。
- 核心功能: QDialog 在 QWidget 的基础上进行了专门化,以更好地服务于 "对话框" 这一特定场景。它并没有从零开始,而是继承了 QWidget 的所有通用功能,然后添加了自己的特性,例如:
- 内置的模态行为支持 (exec())。
- 与父窗口的关系管理,使其可以作为子对话框弹出。
- 对标准按钮和对话框结果(accept() / reject())的标准化处理。
- 更适合对话框的默认窗口标志和样式。
自定义对话框类
我们在工程项目中添加一个自定义类,命名为:NewProjectDlg继承自QDialog。
添加类以后,需要加上Q_OBJECT宏,因为我们要用的Qt的信号与槽的机制,就必须加上这个宏,然后完成构造函数和析构函数的定义:
cpp
#pragma once
#include <QDialog>
class NewProjectDlg :
public QDialog
{
Q_OBJECT
public:
NewProjectDlg(QWidget* parent = nullptr);
~NewProjectDlg();
};
接着我们来重新写一下"新建项目"这个菜单的槽函数,如下:
cpp
void SubCfgTool::createProject()
{
NewProjectDlg* newProDlg = new NewProjectDlg(this);
int ret = newProDlg->exec();// 构建模态对话框
//qDebug() << " project has created!";
}
这里说明一下,对话框的构建分为两类,模态对话框和非模态对话框:
- 模态对话框: 这是最常见的类型。当它弹出时,会阻止用户与父窗口或应用程序的其他部分进行交互,直到用户关闭这个对话框。例如,你在 Word 中选择 "保存" 时弹出的保存对话框就是模态的。QDialog::exec() 是显示模态对话框的标准方式。
- 非模态对话框: 弹出后,用户仍然可以与应用程序的其他窗口进行交互。例如,一些应用中的 "查找" 窗口。QDialog::show() 可以用来显示非模态对话框,但通常需要配合 setModal(false) 和事件循环管理。
测试运行一下效果,点击新建项目如果弹出一个空白的对话框,说明这一步成功。
初始化控件及布局
1、设置标题名称、对话框的大小
在自定义对话框的构造函数中设置对话框的标题和大小,如下:
cpp
NewProjectDlg::NewProjectDlg(QWidget* parent /*= nullptr*/)
{
setWindowTitle(QStringLiteral("新建项目"));
setFixedSize(400, 150);
}
注意:如果中文的标题名称不显示,记得将cpp文件设置高级保存选项,更改编码为:Unicode(UTF-8 带签名)-代码页 65001,具体的操作前面的章节已经介绍过,可以翻阅。
2、添加控件及布局
先定义控件,涉及的控件有QLable、QLineEdit、QPushbutton,引入头文件
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
并开始控件的定义:
cpp
QLabel* labelName = new QLabel(QStringLiteral("项目名称:"));
QLabel* labelPath = new QLabel(QStringLiteral("项目位置:"));
lineEdtProName = new QLineEdit;
lineEdtProDirPath = new QLineEdit;
QPushButton* pushBtnChoosePath = new QPushButton(QStringLiteral("选择"));
QPushButton* pushBtnOk = new QPushButton(QStringLiteral("确定"));
QPushButton* pushBtnCancel = new QPushButton(QStringLiteral("取消"));
如何布局这些控件的位置,以达到我们想要的效果呢。这里就需要用到布局管理器,常用的布局管理器有:水平布局管理器(QHBoxLayout)、垂直布局管理器(QVBoxLayout)、网格布局管理器(QGridLayout),如下:

在本节课的实战场景中,我们会用到这三个布局管理器,引入一下头文件:
#include <QGridLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
其中QGridLayout 是 Qt 中最灵活、最常用的布局管理器之一,核心思想是将界面分割成 "行 × 列" 的网格单元格,每个控件可以占用一个或多个单元格,就像 Excel 表格一样精准排列组件。

我们先来实现一下,内部的网络布局的部分,代码如下:
cpp
QGridLayout *layout1 = new QGridLayout;
layout1->addWidget(labelName, 0, 0, 1, 2);
layout1->addWidget(lineEdtProName, 0, 2, 1, 4);
layout1->addWidget(labelPath, 1, 0, 1, 2);
layout1->addWidget(lineEdtProDirPath, 1, 2, 1, 3);
layout1->addWidget(pushBtnChoosePath, 1, 5, 1, 1);
接下来我们实现按钮部分的水平布局,如下:

我们需要在按钮左侧加入一个可伸缩的区域,如下:
cpp
QHBoxLayout* layout2 = new QHBoxLayout();
layout2->addStretch(1);
layout2->addWidget(pushBtnOk);
layout2->addWidget(pushBtnCancel);
最后再完成总体的垂直方向的布局,如下:
cpp
QVBoxLayout* layout = new QVBoxLayout();
layout->addLayout(layout1);
layout->addLayout(layout2);
setLayout(layout);
运行代码查看效果。 发现界面的样式不太美观,我们在Main函数中加入一行代码:
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); //优化界面显示的清晰度
3、添加控件的响应逻辑
- 选择目录
定义一个选择目录的槽函数:
cpp
public slots:
void selectProjectDirPath();
将槽函数绑定到选择目录按钮的点击事件:
cpp
connect(pushBtnChoosePath, &QPushButton::clicked, this, &NewProjectDlg::selectProjectDirPath);
selectProjectDirPath()槽函数的实现,需要用到QFileDialog类,这个类是Qt 框架中用于实现文件系统对话框的核心类,支持用户选择文件、目录或保存路径,最后将选择的目录添加到文本框中显示,如下:
cpp
void NewProjectDlg::selectProjectDirPath()
{
QString strDirPath = QFileDialog::getExistingDirectory(this, QStringLiteral("选择存放路径"), "/");
if (strDirPath.isEmpty())
{
return;
}
else
{
strDirPath += "/";
lineEdtProDirPath->setText(strDirPath);
}
}
- 确认、取消
参考Qt官方文档,Qt已经为我们提供了两个槽函数,并声明为虚函数。虚函数允许子类进行重写。

- accept()(返回QDialog::Accepted),表示确定
- reject()(返回QDialog::Rejected),表示取消
我们的确定、取消按钮,分别绑定这两个槽函数:
cpp
connect(pushBtnOk, &QPushButton::clicked, this, &NewProjectDlg::accept);
connect(pushBtnCancel, &QPushButton::clicked, this, &NewProjectDlg::reject);
重写一下accept函数,即点击了确定之后执行的内容,其中如何创建工程的核心逻辑结合不同的项目自行定义,我们下一节课讲解本案例的核心实现逻辑。注意最终需要调用QDialog::accept();,因为重写了父类的accept方法,如果不调用父类的这个accept方法,返回值将不能正常返回QDialog::Accepted
cpp
void NewProjectDlg::accept()
{
//TODO: 创建工程的核心逻辑
QDialog::accept();
}
运行测试一下效果,本章节的对话框创建就算完成了,本章的代码会放入评论区,欢迎大家下载。