Qt窗口
前面学习的所有代码都是基于QWidget,比如创建项目选择父类的时候选择的是QWidget,还有前面学习的控件父类都是QWidget。但是QWidget更多的是作为别的窗口的一部分。
我们创建项目的时候还可以选择QDialog对话框和QMainWindow。Qt窗口是通过QMainWindow类来实现的。
QMainWindow是一个为用户提供主窗口程序的类,继承自QWidget类,并且提供了一个预定义的布局。QMainWindow包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个浮动窗口(铆接部件)(dock widgets)、一个状态栏(status bar) 和一个中心部件(central widget),它是许多应用程序的基础,如文本编辑器,图片编辑器等。如下图为QMainwindow中各组件所处的位置:

windowTitle这里就不在赘述,下面可以来看一下菜单栏,比如QtCreator中的菜单栏:

菜单栏只能有一个。
下面看一下工具栏,我们以画图板为例:

上图就是windows画图功能里面的工具栏。工具栏有点类似菜单栏,但是工具栏可以有多个,菜单栏只能有一个。工具栏本质上是把菜单中一些比较常用的选项直接放到工具栏中,这样我们点击工具栏就可以直接快速使用。
下面再说Dock Widget Area,这是个铆接部件,也就是子窗口。我们以Qt Creator为例:

如上图所示,左侧红色方框的文件选项,还有下方的控制台输出窗口,这都是子窗口。
接着再往下就是中心控件了,这是窗口中最核心的部分。然后是最下面的状态栏:

比如画图板最下方有显示像素大小,还可以进行缩放等。
1、菜单栏QMenuBar
Qt中的菜单栏是通过QMenuBar这个类来实现的。一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。菜单栏中包含菜单,菜单中包含菜单项。

如图所示,菜单栏用QMenuBar表示,菜单用QMenu表示,然后菜单中的菜单项用QAction表示。并且菜单中不仅仅可以是菜单项,还可以是菜单。工具栏本质上就是菜单中一些选项的快捷方式。
下面创建一个新项目,以前我们一直选择继承自QWidget类,现在我们要更改继承自QMainWindow。

我们可以创建一些菜单然后运行程序观察一下:

这时候我们点击菜单的时候是没有反应的,因为我们没有给菜单添加菜单项,我们继续通过图形化界面的方式添加菜单项。

此时点击文件就可以看到菜单项,不过此时点击菜单项是没有反应的,毕竟我们还没有关联菜单项对应的处理逻辑。我们可以看一下右侧的对象树,观察一下对象树的结构。
但是我这边使用的是4.11版本的Qt Creator,所以通过图形化界面的方式来创建是会有一些bug的,可能新一点的版本就不会。下面演示代码的方式来实现:
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1.创建菜单栏
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
// 2.创建菜单
QMenu* menu1 = new QMenu("文件");
QMenu* menu2 = new QMenu("编辑");
QMenu* menu3 = new QMenu("视图");
menuBar->addMenu(menu1);
menuBar->addMenu(menu2);
menuBar->addMenu(menu3);
// 3.创建菜单项
QAction* action1 = new QAction("新建");
QAction* action2 = new QAction("打开");
QAction* action3 = new QAction("保存");
QAction* action4 = new QAction("另存为");
QAction* action5 = new QAction("退出");
menu1->addAction(action1);
menu1->addAction(action2);
menu1->addAction(action3);
menu1->addAction(action4);
menu1->addAction(action5);
}

效果如上图所示,不过我们在点击菜单项的时候是没有任何反应的。那么如何让菜单项有反应呢?实际上,当菜单项QAction被点击的时候会触发一个信号,也就是triggered
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1.创建菜单栏
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
// 2.创建菜单
QMenu* menu1 = new QMenu("文件");
QMenu* menu2 = new QMenu("编辑");
QMenu* menu3 = new QMenu("视图");
menuBar->addMenu(menu1);
menuBar->addMenu(menu2);
menuBar->addMenu(menu3);
// 3.创建菜单项
QAction* action1 = new QAction("新建");
QAction* action2 = new QAction("打开");
QAction* action3 = new QAction("保存");
QAction* action4 = new QAction("另存为");
QAction* action5 = new QAction("退出");
menu1->addAction(action1);
menu1->addAction(action2);
menu1->addAction(action3);
menu1->addAction(action4);
menu1->addAction(action5);
// 4.给QAction添加信号槽
connect(action1, &QAction::triggered, this, &MainWindow::handle);
connect(action5, &QAction::triggered, this, &MainWindow::close);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::handle()
{
qDebug() << "触发新建操作!";
}
我们给菜单1和菜单5连接了信号槽,当点击菜单1就会在控制台输出信息,当点击菜单5就会直接关闭窗口。
1、接下来我们要给菜单设置快捷键,我们先看windows上的记事本。

打开记事本我们可以看到上面的菜单,接着我们按一下alt键,就会在下面弹出对应的快捷键。比如对于文件菜单,我们可以通过alt+F就可以打开文件,通过alt+E就可以打开编辑。

当我们用alt+F打开文件后,继续按alt就会在每个菜单项的左侧也显示了对应的快捷键,比如通过alt+X就可以退出。
所以下面我们就要给菜单和菜单项设置快捷键,设置好快捷键后就可以搭配alt来使用。
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建菜单栏
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
// 创建菜单
QMenu* menu1 = new QMenu("文件(&F)");
QMenu* menu2 = new QMenu("编辑(&E)");
QMenu* menu3 = new QMenu("视图(&V)");
QMenu* menu4 = new QMenu("关于(&A)");
menuBar->addMenu(menu1);
menuBar->addMenu(menu2);
menuBar->addMenu(menu3);
menuBar->addMenu(menu4);
// 创建菜单项
QAction* action1 = new QAction("菜单项1");
QAction* action2 = new QAction("菜单项2");
QAction* action3 = new QAction("菜单项3");
QAction* action4 = new QAction("菜单项4");
menu1->addAction(action1);
menu2->addAction(action2);
menu3->addAction(action3);
menu4->addAction(action4);
}

上方创建菜单的时候设置的文本为:文件(&F),通过给文本中添加&F就是添加了alt+F快捷键。最终程序运行&并不会显示出来。这里就和前面讲的QLabel设置伙伴关系那边类似。使用QShortcut也可以实现类似的效果,只不过会更麻烦一些。
下面我们重新创建一个项目,创建两个菜单四个菜单项,给它们都加上快捷键,并给每个菜单项连接信号槽,槽函数做一个简单的输出信息。
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建菜单栏
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
// 创建菜单
QMenu* menu1 = new QMenu("文件(&F)");
QMenu* menu2 = new QMenu("视图(&V)");
menuBar->addMenu(menu1);
menuBar->addMenu(menu2);
// 创建菜单项
QAction* action1 = new QAction("action1(&Q)");
QAction* action2 = new QAction("action1(&W)");
QAction* action3 = new QAction("action1(&E)");
QAction* action4 = new QAction("action1(&R)");
menu1->addAction(action1);
menu1->addAction(action2);
menu2->addAction(action3);
menu2->addAction(action4);
// 给菜单项连接信号槽
connect(action1, &QAction::triggered, this, &MainWindow::handle1);
connect(action2, &QAction::triggered, this, &MainWindow::handle2);
connect(action3, &QAction::triggered, this, &MainWindow::handle3);
connect(action4, &QAction::triggered, this, &MainWindow::handle4);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::handle1()
{
qDebug() << "handle1";
}
void MainWindow::handle2()
{
qDebug() << "handle2";
}
void MainWindow::handle3()
{
qDebug() << "handle3";
}
void MainWindow::handle4()
{
qDebug() << "handle4";
}
前面我们的菜单结构式:菜单栏------>菜单------>菜单项。
但是有的菜单结构可能是:菜单栏------>菜单------>子菜单------>子菜单------>菜单项。
2、QMenuBar可以通过addMenu来添加菜单,而对于QMenu也提供了addMenu可以添加菜单,通过这个操作就可以给某个菜单项添加子菜单。
下面代码实现子菜单结构:
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建菜单栏
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
// 创建菜单
QMenu* menuParent = new QMenu("父菜单");
QMenu* menuChild1 = new QMenu("子菜单1");
menuBar->addMenu(menuParent);
menuParent->addMenu(menuChild1);
// 创建菜单项
QAction* action1 = new QAction("菜单项1");
QAction* action2 = new QAction("菜单项2");
menuChild1->addAction(action1);
menuChild1->addAction(action2);
QMenu* menuChild2 = new QMenu("子菜单2");
menuChild1->addMenu(menuChild2);
}

3、菜单中菜单项很多,可以通过分割线进行分组,下面演示给菜单添加分割线:
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
QMenu* menu = new QMenu("菜单");
menuBar->addMenu(menu);
QAction* action1 = new QAction("菜单项1");
QAction* action2 = new QAction("菜单项2");
menu->addAction(action1);
menu->addSeparator();
menu->addAction(action2);
}

通过addSeparator来添加分割线。
4、给菜单项添加图标,需要先通过qrc引入图片。
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
QMenu* menu = new QMenu("菜单");
menu->setIcon(QIcon(":/open.png"));
menuBar->addMenu(menu);
QAction* action1 = new QAction("菜单项1");
QAction* action2 = new QAction("菜单项2");
action1->setIcon(QIcon(":/open.png"));
action2->setIcon(QIcon(":/save.png"));
menu->addAction(action1);
menu->addAction(action2);
}

如果给QMenu设置图标,当前QMenu是在QMenuBar上的,文本就不会显示,图标覆盖了文本。如果QMenu是子菜单或者是菜单项,图标和文本都是可以显示的。
5、QMenuBar的创建方式。
前面创建QMenuBar的方式都是:
cpp
QMenuBar* menuBar = new QMenuBar();
this->setMenuBar(menuBar);
如果我们创建的项目没有勾选自动生成ui文件,那么上述代码就是OK的。如果勾选了自动生成ui文件,上面的代码会发生内存泄漏。因为如果勾选了自动生成ui文件,Qt会给我们生成一个QMenuBar,我们前面在观察对象树的时候也有看到,QMainWindow下还有QMenuBar和QSatusBar。
所以上面的代码我们又创建了一个QMenuBar,然后设置到窗口中,这时候旧的QMenuBar就会脱离对象树,后续就再也找不到这个QMenuBar了,也就无法进行释放了。
上面的代码如果程序窗口关闭,对象树释放,进程就结束了,所有内存都会回收给操作系统,一个QMenuBar内存泄漏不会造成影响。但是如果这样的代码是出现在一个多窗口的程序中,涉及到窗口的频繁跳转切换,窗口频繁的创建销毁,那么内存泄漏就会更严重一些。但是也都还好,因为QMenuBar本身也不大。而且客户端程序不想服务端,服务端要时时刻刻运行,如果内存泄漏不断积累就会导致服务器内存资源不足。所以服务器程序更害怕内存泄漏。
所以针对上面这个问题,正确的写法如下:
cpp
QMenuBar* menuBar = this->menuBar();
this->setMenuBar(menuBar);
上面代码:1、如果QMenuBar存在,直接获取返回,后续调用setMenuBar是自己替换自己,并不影响。2、如果QMenuBar不存在,就会创建一个新的返回,后续setMenuBar就是设置新的。
2、工具栏QToolBar
工具栏是应用程序中集成各种功能实现快捷键使用的一个区域。可以有多个,也可以没有,它并不是应用程序中必须存在的组件。它是一个可移动的组件,它的元素可以是各种窗口组件,它的元素通常以图标按钮的方式存在。如下图为工具栏的示意图:

下面我们直接实现一个样例:创建一个工具栏,然后给工具栏添加两个菜单项,保存和打开文件,由于工具栏上一般都是直接通过图标显示的,所以我们通过qrc引入图片,让菜单项以图片的方式在工具栏上显示。
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QToolBar>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建工具栏
QToolBar* toolBar = new QToolBar();
this->addToolBar(toolBar);
// 创建菜单项
QAction* action1 = new QAction("打开");
QAction* action2 = new QAction("保存");
action1->setIcon(QIcon(":/open.png"));
action2->setIcon(QIcon(":/save.png"));
toolBar->addAction(action1);
toolBar->addAction(action2);
connect(action1, &QAction::triggered, this, &MainWindow::handle1);
connect(action2, &QAction::triggered, this, &MainWindow::handle2);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::handle1()
{
qDebug() << "handle1";
}
void MainWindow::handle2()
{
qDebug() << "handle2";
}

1、添加菜单栏我们用的是setMenuBar,因为菜单栏只能有一个,重复设置新的就会替换旧的。添加工具栏用的是addToolBar,因为工具栏可以有多个,重复添加就会有多个工具栏。
2、QAction出现在工具栏,也会出现图标覆盖文本的情况,但是前面构造函数设置的文本会以toolTip的方式显示出来,当我们鼠标悬停到图标上,就可以看到我们设置的文本。另外我们也可以用setToolTip来手动设置自己想要的提示信息。
另外,工具栏往往是和菜单栏配合使用的,工具栏的QAction也可以出现在菜单栏中,所以在上面代码的基础上上我们加上菜单栏。
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建菜单栏
QMenuBar* menuBar = this->menuBar();
this->setMenuBar(menuBar);
// 创建菜单
QMenu* menu = new QMenu("文件");
menuBar->addMenu(menu);
// 创建工具栏
QToolBar* toolBar = new QToolBar();
this->addToolBar(toolBar);
// 创建菜单项
QAction* action1 = new QAction("打开");
QAction* action2 = new QAction("保存");
action1->setIcon(QIcon(":/open.png"));
action2->setIcon(QIcon(":/save.png"));
action1->setToolTip("点击这里打开文件");
// 添加菜单项到工具栏
toolBar->addAction(action1);
toolBar->addAction(action2);
// 添加菜单项到菜单中
menu->addAction(action1);
menu->addAction(action2);
connect(action1, &QAction::triggered, this, &MainWindow::handle1);
connect(action2, &QAction::triggered, this, &MainWindow::handle2);
}

那如果一个QAction既是QMenu的子元素,又是QToolBar的子元素,释放对象树的时候,会不会重复delete呢?只会释放一次,不会重复delete。
下面我们创建两个工具栏,看看是什么效果。
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建工具栏
QToolBar* toolBar1 = new QToolBar();
QToolBar* toolBar2 = new QToolBar();
this->addToolBar(toolBar1);
this->addToolBar(toolBar2);
// 创建菜单项
QAction* action1 = new QAction("动作1");
QAction* action2 = new QAction("动作2");
QAction* action3 = new QAction("动作3");
QAction* action4 = new QAction("动作4");
toolBar1->addAction(action1);
toolBar1->addAction(action2);
toolBar2->addAction(action3);
toolBar2->addAction(action4);
}


可以把鼠标放在工具栏最前面的部分,可以拖动工具栏,可以把工具栏拖到窗口的任意位置,比如上图的toolBar1就被我们拖出来了,此时处于浮动状态。我们也可以把他拖到窗口的上/下/左/右。
1、可以设置工具栏出现的初始位置(上/下/左/右)。
2、可以设置工具栏允许停放到哪些边缘。
3、可以设置工具栏是否允许浮动。
4、可以设置工具栏是否可以移动。
下面我们通过代码来实现:
首先是针对1,可以通过addToolBar的重载函数来实现
cpp
void addToolBar(Qt::ToolBarArea area, QToolBar *toolbar);
enum ToolBarArea {
LeftToolBarArea = 0x1,
RightToolBarArea = 0x2,
TopToolBarArea = 0x4,
BottomToolBarArea = 0x8,
ToolBarArea_Mask = 0xf,
AllToolBarAreas = ToolBarArea_Mask,
NoToolBarArea = 0
};
比如设置toolBar2在左侧:

接着后续三个函数:
cpp
void setAllowedAreas(Qt::ToolBarAreas areas); // 设置允许停放边缘
void setFloatable(bool floatable); // 设置是否允许浮动
void setMovable(bool movable); // 设置是否允许移动

3、状态栏QStatusBar
状态栏是应用程序中输出简要信息的区域。一般位于主窗口的最底部,一个窗口中最多只能有一个状态栏。在Qt中,状态栏是通过QStatusBar类来实现的。 在状态栏中可以显示的消息类型有:
- 实时消息:如当前程序状态
- 永久消息:如程序版本号,机构名称
- 进度消息:如进度条提示,百分百提示
下面我们直接通过代码来看一下状态栏是什么样的:

通过showMessage可以在状态栏中显示一个文本,这个文本的存在时间可以自定义,单位为ms。如果时间为0或不设置,消息就会持久存在。
接着给状态栏添加一些控件:

可以通过addWidget来添加控件,该函数是从左往右添加。addPermanentWidget是从右往左添加。
4、浮动窗口QDockWidget
在Qt中,浮动窗口也称之为铆接部件。浮动窗口是通过QDockWidget类来实现浮动的功能。浮动窗口一般是位于核心部件的周围,可以有多个。
浮动窗口我们可以理解为子窗口,比如一个程序有主窗口,主窗口里面又有几个子窗口。
下面直接通过代码来看一下效果。
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建浮动窗口
QDockWidget* dockWidget = new QDockWidget();
this->addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
// 给浮动窗口设置标题
dockWidget->setWindowTitle("这是浮动窗口");
// 给浮动窗口添加控件
QWidget* container = new QWidget();
dockWidget->setWidget(container);
QVBoxLayout* layout = new QVBoxLayout();
container->setLayout(layout);
QLabel* label = new QLabel("这是一个QLabel");
QPushButton* button = new QPushButton("按钮");
layout->addWidget(label);
layout->addWidget(button);
dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::TopDockWidgetArea);
}

1、把QDockWidget添加到主窗口中通过addDockWidget函数,该函数第一个参数要指明要添加的位置,函数声明和参数枚举类型如下。
cpp
void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget);
enum DockWidgetArea {
LeftDockWidgetArea = 0x1,
RightDockWidgetArea = 0x2,
TopDockWidgetArea = 0x4,
BottomDockWidgetArea = 0x8,
DockWidgetArea_Mask = 0xf,
AllDockWidgetAreas = DockWidgetArea_Mask,
NoDockWidgetArea = 0
};
2、QDockWidget不能直接给子窗口添加控件,而是需要创建出一个QWidget然后将控件添加到QWidget中。QDockWidget只能添加一个QWidget,所以如果我们想要添加更多的控件,就只能往QWidget里面添加了。
5、对话框QDialog
5.1、QDialog
对话框是GUI程序中不可或缺的组成部分。一些不适合在主窗口实现的功能组件可以设置在对话框中。对话框通常是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。Qt常用的内置对话框有:QFiledialog(文件对话框)、QColorDialog(颜色对话框)、QFontDialog(字体对话框)、QInputDialog (输入对话框)和 QMessageBox(消息框) 。
比如我们关闭windows画图板的时候就会弹出一个对话框:

针对一个已有的项目,我们可以创建一些类,继承QDialog实现自定义的对话框。当然Qt也提供了上述内置对话框供我们使用。
下面我们创建一个新的项目来看看,这次选择的继承类就是QDialog了。

运行程序后,可以发现右上角有点不同。原来的右上角是最小化、放大、关闭。现在是一个问号和一个关闭。
总的来说,基于QDialog作为父类创建出来的程序窗口和之前通过QWidget创建出来的非常相似。
再看对象树和属性:

对象树就是只有一个QDialog类,然后属性可以发现是继承自QWidget的,所以前面说的QWidget的属性他都有。另外我们需要额外关注的是QDialog自身的modal属性。
实际开发中,往往不是直接在创建项目的时候继承QDialog,而是在代码中创建额外的类继承自QDialog,然后实现自己的对话框。主窗口一般不会作为一个对话框,而是在主窗口中产生出对话框。
下面我们还是重新创建一个项目继承QMainWindow,然后创建一个pushButton通过点击这个按钮来弹出对话框。

但是这里有个问题,就是每次按下按钮都会创建一个新的对话框并显示。只有等到程序结束才会销毁对象树。所以如果程序运行过程中,不断大量的点击按钮,就会导致创建出无数个对象,进一步导致内存泄漏。虽然QMainWindow销毁的时候,对象树上的QDialog也会随之销毁,但是架不住在销毁前创建出很多个QDialog。而且如果你的对话框对象是比较大的呢?或者你机器内存是很少的,比如嵌入式设备。所以我们要解决这个问题。
我们不能直接在调用show之后直接delete,这样对话框就会一闪而过。正确做法是,当用户点击对话框右上角的关闭按钮时,再触发delete释放。所以我们可以把关闭按钮的点击信号和释放QDialog操作的槽函数关联起来。Qt为了让我们更加方便的写代码,给QDialog设置了一个属性,可以直接通过设置属性完成上面的效果。
cpp
dialog->setAttribute(Qt::WA_DeleteOnClose);
只要给dialog设置上述属性,就会关闭的时候进行delete,这是Qt的内置功能。
但是我们用QDialog创建出来的对话框里面空空的什么也没有,正常来说对话框要有一些信息或者按钮。所以下面我们需要自己实现一个类继承QDialog,然后在这个子类中添加一些控件。
首先创建出自定义的Dialog类:

接着我们补充自定义Dialog类的构造函数:
cpp
Dialog::Dialog(QWidget* parent) : QDialog(parent)
{
QVBoxLayout* layout = new QVBoxLayout();
this->setLayout(layout);
QLabel* label = new QLabel("这是一个对话框");
QPushButton* button = new QPushButton("关闭");
layout->addWidget(label);
layout->addWidget(button);
connect(button, &QPushButton::clicked, this, &Dialog::close);
}
然后当点击MainWindow上的按钮后弹出我们自定义的对话框:
cpp
void MainWindow::on_pushButton_clicked()
{
Dialog* dialog = new Dialog(this);
dialog->resize(400, 300);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
效果如下图:

上面是通过代码的方式来实现的,下面我们通过图形化界面的方式来实现,首先创建新项目,并在mainwindow上添加一个按钮,并连接点击信号槽。
关键操作是需要我们创建一个新的ui文件出来,步骤如下:

接着我们在多出来的ui文件中,也就是对话框中添加我们想要的控件。

最后实现一个mainwindow的点击按钮的槽函数,创建一个对话框弹出即可。
cpp
void MainWindow::on_pushButton_clicked()
{
Dialog* dialog = new Dialog();
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
最后来说一下前面讲的一个属性modal,这个是模态的意思。也就是对话框有两种形态:模态和非模态。
模态:弹出对话框的时候,用户无法操作父窗口,必须得完成对话框内部的操作,关闭对话框之后才能操作父窗口。一般用于特别关键的场合,用户必须做出决策。比如前面windows画图板的关闭弹出对话框是否保存。
非模态:弹出对话框的时候,用户可以操作父窗口。我们前面写的所有代码都是非模态的。
那么如何弹出模态对话框呢?很简单,把show()替换成下面的写法:
cpp
dialog->exec();
5.2、QMesssageBox
Qt提供了多种可复用的对话框类型,即Qt标准对话框。Qt标准对话框全部继承 QDialog类。常用标准对话框如下:

QMessageBox用来显示一个消息给用户,并且让用户进行一个简单的选择。代码如下:
cpp
void MainWindow::on_pushButton_clicked()
{
QMessageBox* messageBox = new QMessageBox();
messageBox->setWindowTitle("对话框窗口标题");
messageBox->setText("这是对话框的文本");
messageBox->setIcon(QMessageBox::Warning);
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Save | QMessageBox::Close);
messageBox->exec();
delete messageBox;
}
效果如图:

说明:1、setIcon可以设置我们自己的图标,也可以设置它提供的一些枚举类型图标,如下所示。
cpp
void setIcon(Icon);
enum Icon {
// keep this in sync with QMessageDialogOptions::Icon
NoIcon = 0,
Information = 1,
Warning = 2,
Critical = 3,
Question = 4
};
2、setStandardButtons可以设置按钮,具体的枚举类型按钮如下所示。
cpp
void setStandardButtons(StandardButtons buttons);
enum StandardButton {
// keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton
NoButton = 0x00000000,
Ok = 0x00000400,
Save = 0x00000800,
SaveAll = 0x00001000,
Open = 0x00002000,
Yes = 0x00004000,
YesToAll = 0x00008000,
No = 0x00010000,
NoToAll = 0x00020000,
Abort = 0x00040000,
Retry = 0x00080000,
Ignore = 0x00100000,
Close = 0x00200000,
Cancel = 0x00400000,
Discard = 0x00800000,
Help = 0x01000000,
Apply = 0x02000000,
Reset = 0x04000000,
RestoreDefaults = 0x08000000,
FirstButton = Ok, // internal
LastButton = RestoreDefaults, // internal
YesAll = YesToAll, // obsolete
NoAll = NoToAll, // obsolete
Default = 0x00000100, // obsolete
Escape = 0x00000200, // obsolete
FlagMask = 0x00000300, // obsolete
ButtonMask = ~FlagMask // obsolete
};
3、QMessageBox的场景更多是模态的,所以使用exec函数展示。当弹出对话框的时候,代码就会在exec这里阻塞住直到对话框被关闭。
4、由于使用exec代码会阻塞,所以可以直接用delete删除。或者用setAttribute也是可以的。
上面添加按钮用的是Qt内置的枚举类型,我们也可以自己创建QPushButton并添加,代码如下:
cpp
void MainWindow::on_pushButton_clicked()
{
QMessageBox* messageBox = new QMessageBox();
messageBox->setWindowTitle("对话框窗口标题");
messageBox->setText("这是对话框的文本");
messageBox->setIcon(QMessageBox::Warning);
// messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Save | QMessageBox::Close);
QPushButton* button = new QPushButton("按钮");
messageBox->addButton(button, QMessageBox::AcceptRole);
messageBox->exec();
delete messageBox;
}
addButton函数声明以及第二个参数类型:
cpp
QPushButton *addButton(const QString &text, ButtonRole role);

这个ButtonRole参数就是让你指明按钮的角色,比如接受AcceptRole,或者拒绝RejectRole等等。然后我们也可以对按钮连接信号槽来针对按钮点击后进行一些相关操作。
但是如果使用setStandardButtons就没办法连接信号槽了。这样就无法根据用户点击哪个按钮来实现对应的处理逻辑。实际上我们是有办法的,可以通过exec函数的返回值来判断用户点击的哪个按钮,从而执行对应的处理逻辑。
cpp
void MainWindow::on_pushButton_clicked()
{
QMessageBox* messageBox = new QMessageBox();
messageBox->setWindowTitle("对话框窗口标题");
messageBox->setText("这是对话框的文本");
messageBox->setIcon(QMessageBox::Warning);
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Save | QMessageBox::Close);
// QPushButton* button = new QPushButton("按钮");
// messageBox->addButton(button, QMessageBox::AcceptRole);
int result = messageBox->exec();
if (result == QMessageBox::Ok) {
qDebug() << "OK";
} else if (result == QMessageBox::Save) {
qDebug() << "Save";
} else if (result == QMessageBox::Close) {
qDebug() << "Close";
}
delete messageBox;
}
除了上面创建QMessageBox的方式,还有更加简单的方式可以直接创建QMessageBox。
cpp
void MainWindow::on_pushButton_clicked()
{
int result = QMessageBox::warning(this, "对话框标题", "对话框文本", QMessageBox::Ok | QMessageBox::Cancel);
if (result == QMessageBox::Ok) {
qDebug() << "Ok";
} else if (result == QMessageBox::Cancel) {
qDebug() << "Cancel";
}
}
该函数的具体参数是:父元素指针、窗口标题、对话框正文、按钮。
cpp
inline static int warning(QWidget *parent, const QString &title,
const QString& text,
StandardButton button0, StandardButton button1)
除了warning,还提供了infomation、critical、question。也就是上面枚举的Icon都提供了。通过这种方式就可以简单快速的创建出一个QMessageBox。
5.3、QColorDialog
颜色对话框的功能是允许用户选择颜色。继承自QDialog类。颜色对话框如下图示:

实际开发我们也不会像下面这种方式去创建颜色选择对话框,而是使用类似QMessageBox的专属方法。
cpp
void MainWindow::on_pushButton_clicked()
{
QColorDialog* dialog = new QColorDialog();
dialog->exec();
delete dialog;
}
而是直接采用下面这种方式更快速的创建出一个颜色选择对话框。
cpp
void MainWindow::on_pushButton_clicked()
{
QColor color = QColorDialog::getColor(QColor(0, 255, 0), this, "选择颜色");
qDebug() << color;
// 基于用户选择的颜色设置背景色
char style[1024] = {0};
sprintf(style, "background-color: rgb(%d, %d, %d);", color.red(), color.green(), color.blue());
this->setStyleSheet(style);
}

说明:
1、getColor这个函数可以直接弹出一个模态对话框,用户选择颜色之后点击确定对话框关闭,返回值就是用户选择的颜色。
2、输出的QColor对象,第一个ARGB 1这里表示的是alpha不透明度。然后后面表示的就是rgb,只不过用0-1来表示0-255了。1就对应255,0就对应0。
下面看getColor的函数声明:
cpp
static QColor getColor(const QColor &initial = Qt::white,
QWidget *parent = nullptr,
const QString &title = QString(),
ColorDialogOptions options = ColorDialogOptions());
第一个参数就是弹出来颜色选择对话框初始的颜色,第二个就是父元素指针,第三个就是标题。最后一个参数用缺省的即可,我们不管。
5.4、QFileDialog
文件对话框用于应用程序中需要打开一个外部文件或需要将当前内容存储到指定的外部文件。
主要是以下两个函数:
cpp
QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(),
const QString &dir = QString(), const QString &filter = QString(),
QString *selectedFilter = nullptr, QFileDialog::Options options = Options());
QString getSaveFileName(QWidget *parent = nullptr, const QString &caption = QString(),
const QString &dir = QString(), const QString &filter = QString(),
QString *selectedFilter = nullptr, QFileDialog::Options options = Options());
getOpenFileName用来打开文件,getSaveFileName用来保存数据到文件中。
下面我们实现一个简单的样例:提供两个按钮用来打开文件和保存文件。
cpp
void MainWindow::on_pushButton_clicked()
{
QString path = QFileDialog::getOpenFileName(this);
qDebug() << path;
}
void MainWindow::on_pushButton_2_clicked()
{
QString path = QFileDialog::getSaveFileName(this);
qDebug() << path;
}

说明:
1、这两个函数的返回值是文件的路径,所以我们上面用qDebug()输出了。
2、此处的打开/保存文件的功能需要再额外实现,并不是直接一按保存就真的保存了。
5.5、QFontDialog
Qt中提供了预定义的字体对话框类QFontDialog,用于提供选择字体的对话框部件。
下面实现样例:还是提供一个按钮,点击按钮之后弹出字体选择对话框,选择字体后可以获取到QFont对象,我们可以输出信息查看。甚至将选择的QFont设置到QPushButton中,改变文字样式。
cpp
void MainWindow::on_pushButton_clicked()
{
bool ok;
QFont font = QFontDialog::getFont(&ok);
qDebug() << ok;
qDebug() << font;
qDebug() << font.family();
qDebug() << font.pointSize();
qDebug() << font.bold();
qDebug() << font.italic();
qDebug() << font.underline();
ui->pushButton->setFont(font);
}

QFontDialog::getFont返回一个QFont对象,表示用户选择的字体样式。该函数声明为:
cpp
static QFont getFont(bool *ok, QWidget *parent = nullptr);
第一个参数是输出型参数,表示用户是否选择了字体,选择了则为true,否则为false。
5.6、QInputDialog
Qt中提供了预定义的输入对话框类:QInputDialog,用于进行临时数据输入的场合。
可以让用户输入一个数据,这个数据可以是整数,可以是浮点数,还可以是字符串。
QInputDialog也提供了静态函数如下:

getInt和getDouble只需要说明一下前三个参数就好,后面用缺省值。主要需要说明一下getItem的第四个参数,类型是QStringList,这个就是QList<QString>,可以类比std::list<std::string>。我们需要自己创建一个QStringList items对象,然后添加一些条目进去可以让用户选择。
实现一个样例代码:创建三个按钮,点击按钮分别弹出对话框输入整数、浮点数、条目。
cpp
void MainWindow::on_pushButton_clicked()
{
int result = QInputDialog::getInt(this, "整数输入对话框", "请输入一个整数: ");
qDebug() << result;
}
void MainWindow::on_pushButton_2_clicked()
{
double result = QInputDialog::getDouble(this, "浮点数输入对话框", "请输入一个浮点数: ");
qDebug() << result;
}
void MainWindow::on_pushButton_3_clicked()
{
QStringList items;
items.push_back("1111");
items.push_back("2222");
items.push_back("3333");
items.push_back("4444");
QString item = QInputDialog::getItem(this, "条目输入对话框", "请输入一个条目: ", items);
qDebug() << item;
}

运行效果如上图所示,选择条目的时候会有一个下拉框展示前面添加的items,当然也可以不选择对应的条目直接输入你想要的任何字符串。