Note
对话框是程序与用户”短促对话”的窗口:选个文件、确认删除、输入一个名字。本篇讲清模态(exec)与非模态(show)的本质区别与选型标准,示范如何编写自定义 QDialog 子类并传递返回值,最后过一遍最常用的标准对话框:QFileDialog、QColorDialog、QFontDialog、QInputDialog 和 QMessageBox。适合已完成控件与主窗口学习的读者。
QDialog 是什么
QDialog 是所有对话框类的基类(QFileDialog、QMessageBox 都继承自它)。与普通窗口相比,它有两个外观特征:标题栏右上角没有最小化/最大化按钮;按 Esc 键总是触发 reject() 关闭对话框。
QDialog dialog(this); // 传入父窗口,弹出时居中于父窗口
dialog.setWindowTitle("设置");它最重要的属性不是外观,而是模态性——决定了对话框与父窗口的关系。
模态 exec() 与非模态 show()
两者的区别一句话概括:模态对话框不关闭,父窗口就点不动;非模态对话框两边可以自由切换。
// 模态:程序阻塞在 exec() 这里,直到对话框关闭,返回结果
QDialog dialog(this);
int ret = dialog.exec();
if (ret == QDialog::Accepted) {
// 用户点了"确定"
}
// 非模态:show() 立即返回,对话框与主窗口并存
QDialog *dlg = new QDialog(this); // 注意:必须用 new,不能用栈对象
dlg->show();这里藏着两个关键细节:
- 返回值约定:
exec()返回QDialog::Accepted(1)或QDialog::Rejected(0)。对话框内部的”确定”按钮 connect 到accept()、“取消” connect 到reject(),这两个槽会关闭对话框并设置结果。 - 内存差异:
exec()阻塞,栈上对象在函数返回前一直活着,安全;show()立即返回,栈对象马上析构,对话框会一闪而过甚至崩溃——非模态必须 new 出来并指定父对象,让 Qt 父子机制管理其生命周期。
Warning
最常见的崩溃原因就是非模态对话框用了栈对象:
QDialog dlg(this); dlg.show();——show 返回后 dlg 立刻销毁,界面瞬间消失。非模态对话框一律用new创建、传父对象。
选型标准:需要用户必须先回答才能继续的(打开文件、确认删除、错误提示)用模态;需要与主窗口保持互动的(查找替换、浮动调参面板)用非模态。Qt 的通用对话框内部都是模态的。
自定义 QDialog 子类与数据返回
真正的项目里对话框都要自己定制。套路很固定:继承 QDialog,构造函数里搭控件和按钮,确定按钮 connect 到 accept(),对外提供读取用户输入的接口:
// namedialog.h
#include <QDialog>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QVBoxLayout>
class NameDialog : public QDialog
{
Q_OBJECT
public:
explicit NameDialog(QWidget *parent = nullptr) : QDialog(parent)
{
setWindowTitle("输入名称");
m_edit = new QLineEdit(this);
// 标准按钮盒:确定/取消自动绑定 accept()/reject()
QDialogButtonBox *btns = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(btns, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(btns, &QDialogButtonBox::rejected, this, &QDialog::reject);
QVBoxLayout *lay = new QVBoxLayout(this);
lay->addWidget(m_edit);
lay->addWidget(btns);
}
QString name() const { return m_edit->text(); } // 对外读取接口
private:
QLineEdit *m_edit;
};调用方拿到返回值后读取数据:
NameDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
qDebug() << "用户输入:" << dlg.name();
}Success
QDialogButtonBox 是对话框按钮的标配:自动处理按钮顺序的平台差异(Windows 与 macOS 确认/取消位置相反),并自动连接 accept/reject。传数据给对话框用构造参数或 setter,取数据用 getter——让数据流向清晰单向。
标准对话框四件套
Qt 把高频需求做成了静态函数,一行代码弹窗取值,无需自建类。
QFileDialog 选文件:
QString file = QFileDialog::getOpenFileName(
this, tr("打开图片"), QString(),
tr("图片文件 (*.png *.jpg);;所有文件 (*)"));
if (!file.isEmpty()) { /* 用户确实选了文件 */ }第三个参数是初始目录,第四个是过滤器,多种类型用 ;; 分隔。同类还有 getSaveFileName(保存)和 getExistingDirectory(选目录)。
QColorDialog 取颜色:
QColor color = QColorDialog::getColor(Qt::white, this);
if (color.isValid()) { // 用户按了取消会返回非法颜色,必须判断
widget->setStyleSheet(QString("background: %1").arg(color.name()));
}QFontDialog 取字体:
bool ok = false;
QFont font = QFontDialog::getFont(&ok, this);
if (ok) { editor->setFont(font); } // 用 ok 标志判断用户是否取消QInputDialog 输入简短内容:整数、浮点数、文本、下拉项各有对应静态函数:
bool ok = false;
QString text = QInputDialog::getText(
this, tr("重命名"), tr("新名称:"), QLineEdit::Normal, "默认值", &ok);
int count = QInputDialog::getInt(this, tr("数量"), tr("份数:"), 1, 1, 99);
if (ok) { /* 使用 text / count */ }Warning
取消操作的判断方式三件套各不相同:QColorDialog 用
isValid(),QFontDialog 和 QInputDialog 用bool *ok指针,QFileDialog 用返回字符串是否为空。不判断就使用返回值,用户按取消时会写入非法数据。
QMessageBox:消息与确认
QMessageBox 是 QDialog 的派生类,用于提示信息或让用户做选择。静态函数最常用,返回用户点击的按钮枚举:
// 提示信息
QMessageBox::information(this, tr("完成"), tr("文件已保存"));
// 询问:三个按钮,判断用户点了哪个
int ret = QMessageBox::question(
this, tr("确认删除"),
tr("确定要删除这条记录吗?此操作不可恢复。"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::No); // 默认焦点在 No,防误触
if (ret == QMessageBox::Yes) { deleteRecord(); }
// 警告与错误
QMessageBox::warning(this, tr("警告"), tr("磁盘空间不足"));
QMessageBox::critical(this, tr("错误"), tr("无法打开文件"));四个静态函数 information / question / warning / critical 只是图标和默认按钮不同,参数一致。需要更复杂的消息框(附加复选框、详细文本)时,用对象方式逐步设置:
QMessageBox box(this);
box.setWindowTitle(tr("保存更改"));
box.setText(tr("文档已被修改。"));
box.setInformativeText(tr("是否保存修改后的内容?"));
box.setStandardButtons(QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel);
box.setDefaultButton(QMessageBox::Save);
int ret = box.exec(); // 与 QDialog 的 exec 同一套返回值体系完整可运行示例
把自定义对话框与标准对话框串起来,Qt5 / Qt6 兼容:
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QLabel>
#include <QFileDialog>
#include <QColorDialog>
#include <QFontDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QLineEdit>
// 自定义对话框:输入名称
class NameDialog : public QDialog
{
public:
explicit NameDialog(QWidget *parent = nullptr) : QDialog(parent)
{
setWindowTitle("输入名称");
m_edit = new QLineEdit(this);
QDialogButtonBox *btns = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(btns, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(btns, &QDialogButtonBox::rejected, this, &QDialog::reject);
QVBoxLayout *lay = new QVBoxLayout(this);
lay->addWidget(m_edit);
lay->addWidget(btns);
}
QString name() const { return m_edit->text(); }
private:
QLineEdit *m_edit;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("对话框示例");
window.resize(320, 200);
QLabel *status = new QLabel("点击下方按钮体验各种对话框");
QPushButton *btnCustom = new QPushButton("自定义对话框(模态)");
QPushButton *btnFile = new QPushButton("标准对话框:选文件");
QPushButton *btnColor = new QPushButton("标准对话框:取颜色");
// 模态:exec() 返回后才能继续
QObject::connect(btnCustom, &QPushButton::clicked, [&status](){
NameDialog dlg;
if (dlg.exec() == QDialog::Accepted)
status->setText(QString("你输入了:%1").arg(dlg.name()));
else
status->setText("你取消了输入");
});
// 标准对话框静态函数:一行弹窗
QObject::connect(btnFile, &QPushButton::clicked, [&status](){
QString f = QFileDialog::getOpenFileName(
nullptr, "打开文件", QString(), "文本文件 (*.txt)");
status->setText(f.isEmpty() ? "未选择文件" : QString("选中:%1").arg(f));
});
QObject::connect(btnColor, &QPushButton::clicked, [&window](){
QColor c = QColorDialog::getColor(Qt::white, &window);
if (c.isValid())
window.setStyleSheet(
QString("background: %1").arg(c.name()));
});
QVBoxLayout *lay = new QVBoxLayout(&window);
lay->addWidget(status);
lay->addWidget(btnCustom);
lay->addWidget(btnFile);
lay->addWidget(btnColor);
window.show();
return app.exec();
}Warning
静态函数弹出的对话框都是模态的,会阻塞当前函数直到用户响应。不要在计算密集的后台线程里直接调它们——对话框属于界面线程,跨线程创建控件是 Qt 明令禁止的行为。
Success
判断该用哪种对话框的经验法则:一次性获取数据用模态 + exec()(大多数场景);持续调整参数用非模态 + 信号槽同步。拿不准时先模态,实际用起来别扭再改非模态,改造成本很低。
自测:三个问题检验掌握程度
- 为什么非模态对话框必须用 new 创建?栈对象会出什么问题?
- QColorDialog、QFontDialog、QFileDialog 各自如何判断”用户点了取消”?
- 自定义对话框中,“确定”按钮应该 connect 到哪个槽?exec() 的返回值如何约定? (答案都在上文)