对话框是 GUI 程序中不可或缺的组成部分,对话框通常会是一个顶层窗口出现在程序最上层,用于实现短期任务或者简单用户交互。
Qt 中使用QDialog类实现对话框,通常会设计一个类继承QDialog。如果QDialog 的 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时其默认出现的位置是 parent 的中心),顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。
标准对话框是 Qt 内置的一系列对话框用于简化开发,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同,因此没有必要在每一个程序中都自己实现这么一个对话框。
Qt 的内置的标准对话框大致分为以下几类:
对话框分为模态对话框和非模态对话框,
Qt 支持模态对话框和非模态对话框,模态与非模态的实现:
QDialog::exec()
实现应用程序级别的模态对话框QDialog::open()
实现窗口级别的模态对话框QDialog::show()
实现非模态对话框。Qt 有两种级别的模态对话框:
应用程序级别的模态(默认):
当该种模态的对话框出现时,用户必须首先对对话框进行交互,直到关闭对话框,然后才能访问程序中其他的窗口。
窗口级别的模态:
该模态仅仅阻塞与对话框关联的窗口,但是依然允许用户与程序中其它窗口交互。窗口级别的模态尤其适用于多窗口模式。
调用exec()
将对话框显示出来(模态对话框),当对话框出现时用户不能与主窗口进行任何交互,直到关闭了该对话框。
QDialog dialog(this);//对象创建在栈上(匿名函数释放后 dialog对象会释放)
dialog.resize(400, 300);
dialog.setWindowTitle("modal dialog");
dialog.exec();
qDebug() << "modal dialog poped up.";
下面将 exec()
修改为 show()
定义出非模态对话框:
QDialog dialog(this);
dialog->resize(400, 300);
dialog->setWindowTitle("modalless dialog");
dialog->show();
qDebug() << "modalless dialog poped up.";
对话框竟然一闪而过,这是因为 show()
函数不会阻塞当前线程对话框会显示出来,然后函数立即返回代码继续执行。dialog 是建立在栈上的,当show()函数返回MainWindow::open()函数结束,dialog 超出作用域被析构,因此对话框消失了。
将 dialog 改成堆上建立,就不会出现这个问题了:
QDialog *dialog = new QDialog(this);//对象创建在堆区(匿名函数释放后 dialog对象不会释放)
dialog->resize(400, 300);
dialog->setWindowTitle("modalless dialog");
dialog->show();
qDebug() << "modalless dialog poped up.";
上面的代码是有问题的dialog 存在内存泄露,dialog 使用 new 在堆上分配空间却一直没有 delete。解决方案也很简单:将 MainWindow 的指针赋给 dialog 即可,利用对象树自动析构释放内存。
不过这样做存在问题:
在这种情景下可以设置dialog的WindowAttribute解决:函数设置对话框关闭时,自动销毁对话框。
QDialog *dialog = new QDialog(this);//对象创建在堆区(匿名函数释放后 dialog对象不会释放)
dialog->resize(400, 300);
dialog->setWindowTitle("modalless dialog");
dialog->setAttribute(Qt::WA_DeleteOnClose);//防止用户重复操作 多次在堆区开辟内存 导致内存泄露
dialog->show();
qDebug() << "modalless dialog poped up.";
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent),ui(new Ui::MainWindow) {
ui->setupUi(this);
//点击按钮 弹出对话框
connect(ui->actionnew, &QAction::triggered, this, [=](){
QDialog dialog(this);//对象创建在栈上(匿名函数释放后 dialog对象会释放)
dialog.resize(400, 300);
dialog.setWindowTitle("modal dialog");
dialog.exec();
qDebug() << "modal dialog poped up.";
});
connect(ui->actionopen, &QAction::triggered, this, [=](){
QDialog *dialog = new QDialog(this);//对象创建在堆区(匿名函数释放后 dialog对象不会释放)
dialog->resize(400, 300);
dialog->setWindowTitle("modalless dialog");
dialog->setAttribute(Qt::WA_DeleteOnClose);//防止用户重复操作 多次在堆区开辟内存 导致内存泄露
dialog->show();
qDebug() << "modalless dialog poped up.";
});
}
MainWindow::~MainWindow() {
delete ui;
}
QMessageBox用于显示消息提示。我们一般会使用其提供的几个 static 函数:
使用QMessageBox::question()来询问一个问题,关于函数参数的解释:
这个对话框的父窗口是 this。QMessageBox是QDialog的子类,这意味着它的初始显示位置将会是在 parent 窗口的中央。
第二个参数是对话框的标题。
第三个参数是我们想要显示的内容。
第四个参数是关联的按键类型,我们可以使用或运算符(|)指定对话框应该出现的按钮。比如我们希望是一个 Yes 和一个 No。
最后一个参数指定默认选择的按钮。这个函数有一个返回值,用于确定用户点击的是哪一个按钮。按照我们的写法,应该很容易的看出,这是一个模态对话框,因此我们可以直接获取其返回值。
QMessageBox使用案例:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent),ui(new Ui::MainWindow) {
ui->setupUi(this);
//点击按钮 弹出对话框
connect(ui->actionnew, &QAction::triggered, this, [=](){
QDialog dialog(this);//对象创建在栈上(匿名函数释放后 dialog对象会释放)
dialog.resize(400, 300);
dialog.setWindowTitle("modal dialog");
dialog.exec();
qDebug() << "modal dialog poped up.";
});
connect(ui->actionopen, &QAction::triggered, this, [=](){
QDialog *dialog = new QDialog(this);//对象创建在堆区(匿名函数释放后 dialog对象不会释放)
dialog->resize(400, 300);
dialog->setWindowTitle("modalless dialog");
dialog->setAttribute(Qt::WA_DeleteOnClose);//防止用户重复操作 多次在堆区开辟内存 导致内存泄露
dialog->show();
qDebug() << "modalless dialog poped up.";
});
//点击按钮 弹出消息对话框
connect(ui->actionwelcome, &QAction::triggered, this, [=](){
QMessageBox::information(this, "welcome", "welcome to this application!~ ");
qDebug() << "critical message box poped up.";
});
connect(ui->actionedit, &QAction::triggered, this, [=](){
QMessageBox::critical(this, "sorry", "being developing, looking forward to more content.");
qDebug() << "critical message box poped up.";
});
connect(ui->actionhelp, &QAction::triggered, this, [=](){
QMessageBox::StandardButton choose;
choose = QMessageBox::question(this, "question?", "Is there any problem when using this product? ");
if (choose == QMessageBox::Yes) {
qDebug() << "User have question about the use of the software.";
} else if (choose == QMessageBox::No) {
qDebug() << "User have no question about the use of the software.";
}
qDebug() << "question message box poped up.";
});
connect(ui->actionproject, &QAction::triggered, this, [=](){
QMessageBox::warning(this, "warning", "project cannot be edit now.");
qDebug() << "question message box poped up.";
});
}
MainWindow::~MainWindow() {
delete ui;
}
自定义细节QMessageBox细节:
QMessageBox类的 static 函数优点是方便使用,缺点是非常不灵活。只能使用简单的几种形式。为了能够定制QMessageBox细节,必须使用QMessageBox的属性设置 API。如果希望制作一个询问是否保存的对话框,可以使用如下的代码:
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent),ui(new Ui::MainWindow) {
ui->setupUi(this);
//点击save按钮 弹出save对话框(模态对话框)
connect(ui->actionsave, &QAction::triggered, this, [=](){
QMessageBox msgBox(this);
msgBox.setText(tr("The document has been modified."));
msgBox.setInformativeText(tr("Do you want to save your changes?"));
msgBox.setDetailedText(tr("Differences here..."));
msgBox.setStandardButtons(QMessageBox::Save
| QMessageBox::Discard
| QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Save:
qDebug() << "Save document!";
break;
case QMessageBox::Discard:
qDebug() << "Discard changes!";
break;
case QMessageBox::Cancel:
qDebug() << "Close document!";
break;
}
});
}
msgBox 是一个建立在栈上的QMessageBox实例。
设置其主要文本信息为 The document has been modified.,informativeText 则是会在对话框中显示的简单说明文字。使用了detailedText详细信息,当我们点击了详细信息按钮时,对话框可以自动显示更多信息。
自定义的对话框的按钮有三个:保存、丢弃和取消。最后我们使用了exec()是其成为一个模态对话框,根据其返回值进行相应的操作。
首先需要创建一个带有文本编辑功能的窗口:
QAction *openAction = ui->actionopen;
QAction *saveAction = ui->actionsave;
//设置按钮图片
openAction->setIcon(QIcon(":/res/img/ParticleSmoke.png"));
saveAction->setIcon(QIcon(":/res/img/filetransfer.png"));
//设置按钮提示tips
openAction->setStatusTip(tr("Open an existing file"));
saveAction->setStatusTip(tr("Save a new file"));
//设置中心组件为textEdit
QTextEdit *textEdit = new QTextEdit(this);
使用connect()函数,为这两个QAction对象添加响应的动作:
connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
connect(saveAction, &QAction::triggered, this, &MainWindow::saveFile);
编写核心的逻辑处理openFile()
和 saveFile()
函数:
//打开文件
void MainWindow::openFile() {
QString filepath = QFileDialog::getOpenFileName(this, tr("Open File"), ".", tr("Text Files(*.txt)"));
if(!filepath.isEmpty()) {
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, tr("Read File"), tr("Cannot open file:\n%1").arg(filepath));
return;
}
QTextStream in(&file);
textEdit->setText(in.readAll());
file.close();
} else {
QMessageBox::warning(this, tr("Path"), tr("You did not select any file."));
}
}
//保存文件
void MainWindow::saveFile() {
QString filepath = QFileDialog::getSaveFileName(this, tr("Open File"), ".", tr("Text Files(*.txt)"));
if(!filepath.isEmpty()) {
QFile file(filepath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, tr("Write File"), tr("Cannot open file:\n%1").arg(filepath));
return;
}
QTextStream out(&file);
out << textEdit->toPlainText();
file.close();
} else {
QMessageBox::warning(this, tr("Path"), tr("You did not select any file."));
}
}
在openFile()函数中,我们使用QFileDialog::getOpenFileName()来获取需要打开的文件的路径。这个函数原型如下:
QString getOpenFileName(QWidget * parent = 0,
const QString & caption = QString(),
const QString & dir = QString(),
const QString & filter = QString(),
QString * selectedFilter = 0,
Options options = 0)
parent:父窗口,Qt 的标准对话框提供静态函数,用于返回一个模态对话框;
caption:对话框标题,
dir:对话框打开时的默认目录
.
代表程序运行目录
/
代表当前盘符的根目录(特指 Windows 平台;Linux 平台当然就是根目录),这个参数也可以是平台相关的,比如“C:\”等;
filter:过滤器,我们使用文件对话框可以浏览很多类型的文件,但是,很多时候我们仅希望打开特定类型的文件。比如,文本编辑器希望打开文本文件,图片浏览器希望打开图片文件。过滤器就是用于过滤特定的后缀名。如果我们使用“Image Files(*.jpg *.png)”,则只能显示后缀名是 jpg 或者 png 的文件。如果需要多个过滤器,使用“;;”分割,比如“JPEG Files(*.jpg);;PNG Files(*.png)”;
selectedFilter:默认选择的过滤器;
options:对话框的一些参数设定,比如只显示文件夹等等,它的取值是enum QFileDialog::Option,每个选项可以使用 | 运算组合起来。
在saveFile()
中使用的QFileDialog::getSaveFileName()
也是类似的。使用这种静态函数,在 Windows、Mac OS 上面都是直接调用本地对话框,但是 Linux 上则是QFileDialog自己的模拟。这表明如果你不使用这些静态函数,而是直接使用QFileDialog进行设置,那么得到的对话框很可能与系统对话框的外观不一致(需要注意的)。
//颜色对话框
connect(ui->actioncolor, &QAction::triggered, this, [=](){
QColor color = QColorDialog::getColor(QColor(255, 0, 0));
qDebug() << "red = " << color.red() << "green = " << color.green() << "blue = " << color.blue();
});
//字体对话框
connect(ui->actionfont, &QAction::triggered, this, [=](){
bool flag;
QFont font = QFontDialog::getFont(&flag, QFont("方正喵呜简体", 18));
qDebug() << "font-family:" << font.family() << "font-size:" << font.pointSize()
<< "isBold:" << font.bold() << "isitalic:" << font.italic();
qDebug() << "QFileDialog poped up.";
});