目录
模态和非模态对话框
当一个对话窗口弹出时,如果你不对它进行操作就无法操作其它窗口,这种对话框就成为模态对话框。反之则称为非模态对话框。
模态对话框
在Qt中,模态对话框是如何实现的呢?不仅需要用到QDialog类,还需要用到exec()。
在Qt的框架中有提到过,exec()使程序进入一个循环,直到用户进行操作才结束循环。所以,要实现模态对话框也需要用到它,只有当用户对对话框作出反馈,才能继续后续的操作。
下面用几行代码体会什么是模态对话框:
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QMenuBar>
#include <QMenu>
#include <QDebug>
#include <QToolBar>
#include <QPushButton>
#include <QStatusBar>
#include <QLabel>
#include <QTextEdit>
#include <QDockWidget>
#include <QDialog> //实现对话框必须引入的头文件
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//这部分代码省略
QDialog *pDialog=new QDialog(this);
pDialog->exec(); //注意是QDialog对象调用exec方法
qDebug()<<"用户对模态对话框进行操作了";
}
MainWindow::~MainWindow()
{
delete ui;
}
实现效果:
由于这里没有往对话框里添加任何内容,所以出现的就是这么简单一个对话框,并且控制台中没有打印内容。下面我们点击“×”,关闭对话框。效果如下:
可以看到对话框消失了,主窗口才显示出来,同时控制台打印“用户对模态对话框进行操作了”,也就是说当用户对模态对话框进行反馈的动作之后,才会执行位于exec()方法后的语句。
注意:这里为什么对话框会先于主窗口出现呢?因为对话框定义在主窗口的构造函数中,包括它调用的exec方法,而主窗口只在自身的构造函数结束后,并调用show方法才会出现。
如果将一个模态对话框定义在main.cpp中会怎么样?下面来看看这两种情况:
#include "MainWindow.h"
#include <QApplication>
#include <QDialog>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog *pDialog=new QDialog();
pDialog->exec();
MainWindow w;
w.show();
return a.exec();
}
实现效果:
此时,只显示对话框,接着我们点击关闭按钮:
点击关闭按钮后,才执行exec()后面的语句。因此,这时候主窗口才显示出来。
刚才,我们将模态对话框定义在主窗口之前,如果模态对话框定义在主窗口之后会发生什么?
#include "MainWindow.h"
#include <QApplication>
#include <QDialog>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
QDialog *pDialog=new QDialog();
pDialog->exec();
return a.exec();
}
实现效果:
虽然很糊(-_-||)还是可以看到,模态对话框一开始是被主窗口覆盖的,但是马上又跑到最前面了。 而且如果不关闭对话框,是没有办法操作后面的主窗口的。
非模态对话框
如果要实现非模态对话框,只要将exec()改为show()方法即可。
下面我们来实现点击菜单栏的“新建”按钮就会弹出一个非模态对话框的功能,代码如下:
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QMenuBar>
#include <QMenu>
#include <QDebug>
#include <QToolBar>
#include <QPushButton>
#include <QStatusBar>
#include <QLabel>
#include <QTextEdit>
#include <QDockWidget>
#include <QDialog>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//这部分代码省略,pCreate为QAction对象指针,在省略的代码中已定义
connect(pCreate,&QAction::triggered,[=](){
QDialog mDialog;
mDialog.show();
qDebug()<<"1111";
});
}
MainWindow::~MainWindow()
{
delete ui;
}
实现效果:
可以看到(?)点击“新建”后,确实出现了一个对话框,但是马上就消失了。同时,控制台也打印了“1111”的内容,说明非模态对话框不需要等到用户进行反馈才执行show()方法之后的代码。
但是!为什么对话框会消失呢?
这是因为,这个对话框mDialog是定义在栈里面的,同时还是一个局部变量,出了函数体后就会被内存自动回收,它就不存在了,所以也就消失了。
要解决这个问题,可以把mDialog作为主窗口类的成员变量,也可以用 new 关键字动态地为它申请内存,这样,就只有当我们用 delete 关键字手动释放这块内存时,它才会消失。
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QMenuBar>
#include <QMenu>
#include <QDebug>
#include <QToolBar>
#include <QPushButton>
#include <QStatusBar>
#include <QLabel>
#include <QTextEdit>
#include <QDockWidget>
#include <QDialog>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//这部分代码省略,pCreate为QAction对象指针,在省略的代码中已定义
connect(pCreate,&QAction::triggered,[=](){
QDialog *mDialog=new QDialog(this); //动态申请内存
mDialog->show();
qDebug()<<"1111";
});
}
MainWindow::~MainWindow()
{
delete ui;
}
实现效果:
这下,对话框就老老实实呆在这里了。当我们关闭主窗口时,对话框也会一起消失。此时,虽然我们没有手动用 delete 释放内存,但是由于它的父元素(也就是主窗口)在被关闭时,程序随之结束,它的析构函数被调用了,因此它下面所有子元素的析构也会被调用,并且先于主窗口被析构。【详见:https://blog.csdn.net/weixin_41001497/article/details/107181513】
注意:用动态申请内存这种方式有一个缺点,就是它只有在程序结束时才释放内存,如果我们经常点击“新建”按钮,就会多次动态申请内存,这样一来也会造成内存泄漏的问题。所以,最好还是将对话框作为主窗口类的成员变量来定义。或者,在动态申请内存空间后,加上在每次关闭对话框时自动释放内存的一句代码。
QDialog *mDialog=new QDialog(this); mDialog->setAttribute(Qt::WA_DeleteOnClose); //关闭对话框的同时,回收内存 mDialog->show();
标准对话框和文件对话框
标准对话框
在实际开发中,我们经常见到的还有标准对话框和文件对话框,什么是标准对话框?看图:
是不是非常熟悉?标准对话框是一种模式对话框,但是往往不直接用模式对话框来实现,因为这些按钮需要我们人为添加上去,要实现上面这种样式的对话框,有更简单的方法。
首先,要实现标准对话框需要先引入<QMessageBox>头文件。
about()
如果要实现一个程序中常常用来向用户描述介绍软件产品,并且只需要用户点击一个类似于“我知道了”的按钮的对话框,需要用到about方法。
调用QMessageBox类的about()函数,它的参数通过帮助文档可以知道,分别有“父元素”、“标题”、“内容”。
#include "MainWindow.h"
#include <QMenuBar>
#include <QMenu>
#include <QDebug>
#include <QToolBar>
#include <QPushButton>
#include <QStatusBar>
#include <QLabel>
#include <QTextEdit>
#include <QDockWidget>
#include <QDialog>
#include <QMessageBox> //要使用标准对话框,必须引入的头文件
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//这部分代码省略,pCreate是一个QAction指针变量,在省略代码中已定义
connect(pCreate,&QAction::triggered,[=](){
QMessageBox::about(this,"create","新建");
});
}
MainWindow::~MainWindow()
{
}
实现效果:
可以看到点击“新建”按钮,会弹出一个标题为“create”,内容为“新建”的模态对话框。
P.S:
用下面的代码实现标准对话框,看起来效果也是一样的。
connect(pCreate,&QAction::triggered,[=](){ QMessageBox pMessageBox; pMessageBox.about(this,"haha","hoho"); //QMessageBox::about(this,"create","新建"); });
question()
如果要实现一个询问用户是否进行某项操作的对话框,需要用到question方法。
调用QMessageBox类的question()函数,它的参数通过帮助文档可以知道,分别有“父元素”、“标题”、“内容”、“第一个按钮的值”、“第二个按钮的值”、“第三个按钮的值”。其中,第二个和第三个按钮的值默认为0,也就是说如果不对其进行设置的话,就认为是NoButton,也就是没有第二、三个按钮。
下面是只指定了第一个按钮的值的情况:
connect(pCreate,&QAction::triggered,[=](){
QMessageBox pMessageBox;
pMessageBox.question(this,"haha","hahaha",QMessageBox::Yes);
});
实现效果:
下面是指定了三个按钮的值的情况:
connect(pCreate,&QAction::triggered,[=](){
QMessageBox pMessageBox;
pMessageBox.question(this,"haha","hahaha",QMessageBox::Yes,QMessageBox::No,QMessageBox::Cancel);
});
实现效果:
注意:按钮不能超过三个,否则会报错。
E:\Demo\QtDemo\BilibiliTest\06_QDialog\MainWindow.cpp:74: error: C2664: “int QMessageBox::question(QWidget *,const QString &,const QString &,QMessageBox::StandardButton,QMessageBox::StandardButton)”: 无法将参数 4 从“QMessageBox::StandardButton”转换为“const QString &”
下面是只给出前三个参数的值,没有给出按钮的值的情况:
connect(pCreate,&QAction::triggered,[=](){
QMessageBox pMessageBox;
pMessageBox.question(this,"haha","hahaha");
});
实现效果:
QMessage::question()的返回值为int型,返回的是被用户点击的按钮对应的值,比如点击Yes返回16384(0x4000),点击No返回65535(0x10000),利用这个特性,可以配合switch语句,来处理用户不同的选择。 比如:
connect(pCreate,&QAction::triggered,[=](){
QMessageBox pMessageBox;
int ret=pMessageBox.question(this,"haha","hahaha");
switch(ret){
case QMessageBox::Yes:
qDebug()<<"用户点击了Yes";
break;
case QMessageBox::No:
qDebug()<<"用户点击了No";
break;
defaul:
break;
}
});
先后点击对话框的Yes按钮和No按钮,实现效果:
文件对话框
文件对话框也是很常用的,比如我们经常需要在一个程序中打开一个已存在的项目,这时候就会弹出一个文件对话框。当我们选择完目标文件并点击确定后,文件完整的绝对路径就会作为一个字符串返回给我们。
要使用文件对话框,必须先引入<QFileDialog>头文件。
如果我们要获取打开的文件的路径,需要调用getOpenFileName方法。
P.S:
第一个参数parent,用于指定父组件。注意,很多Qt组件的构造函数都会有这么一个parent参数,并提供一个默认值0;
第二个参数caption,是对话框的标题;
第三个参数dir,是对话框显示时默认打开的目录,"." 代表程序运行目录,"/" 代表当前盘符的根目录(Windows,Linux下/就是根目录了),也可以是平台相关的,比如"C:\\"等;例如我想打开程序运行目录下的Data文件夹作为默认打开路径,这里应该写成"./Data/",若想有一个默认选中的文件,则在目录后添加文件名即可:"./Data/teaser.graph"
第四个参数filter,是对话框的后缀名过滤器,比如我们使用"Image Files(*.jpg *.png)"就让它只能显示后缀名是jpg或者png的文件。如果需要使用多个过滤器,使用";;"分割,比如"JPEG Files(*.jpg);;PNG Files(*.png)";
第五个参数selectedFilter,是默认选择的过滤器;
第六个参数options,是对话框的一些参数设定,比如只显示文件夹等等,它的取值是enum QFileDialog::Option,每个选项可以使用 | 运算组合起来。
如果我要想选择多个文件怎么办呢?Qt提供了getOpenFileNames()函数,其返回值是一个QStringList。你可以把它理解成一个只能存放QString的List,也就是STL中的list<string>。【转自:https://blog.csdn.net/qq_38400517/article/details/78897446】
connect(pCreate,&QAction::triggered,[=](){
QFileDialog::getOpenFileName(this,"open","../"); //相对路径是当前程序所在路径的上级目录
});
实现效果:
如果我们还想指定文件类型的范围,则需要设定filter参数。这里同一个类型的不同文件后缀名用空格分隔,不同类型的文件用 ;; 分隔。
connect(pCreate,&QAction::triggered,[=](){
QFileDialog::getOpenFileName(this,"open","../","source(*.cpp *.h);;image(*.jpg *.bmp);;all(*.*)");
});
P.S:如果觉得字符串太长,可以用Enter键进行换行,C++会自动添加上缺少的双引号。
实现效果:
P.S:如有错误,欢迎指正~