28、Qt对话框:QMessageBox、QFileDialog、QColorDialog、QFontDialog、自定义对话框

对话框这东西,说白了就是程序跟用户「对话」的小窗口。你想想看,用户点了个「保存」,你得问「存到哪?」;用户删了数据,你得确认「你确定吗?」;用户想换个字体颜色,你得给他个调色板。这些场景,Qt 都帮我们封装好了现成的对话框类。

我个人习惯把对话框分成两类:标准对话框自定义对话框。标准对话框就是 Qt 自带的那些,拿来就能用;自定义对话框呢,就是你觉得现成的不够用,自己动手撸一个。

核心知识点一览:

  • QMessageBox:消息提示、警告、错误、询问
  • QFileDialog:打开文件、保存文件、选择目录
  • QColorDialog:颜色选择器
  • QFontDialog:字体选择器
  • 自定义对话框:继承 QDialog,自己设计界面
Qt 对话框体系 QDialog QMessageBox QFileDialog QColorDialog QFontDialog 继承 QDialog 添加 Widget + Layout exec() / accept() / reject() 标准对话框 自定义对话框

28.1 QMessageBox:最常用的消息框

QMessageBox 是我用得最多的对话框,没有之一。你想想看,程序里到处都需要提示用户:操作成功、操作失败、确认删除、询问是否保存……这些全用它。

Qt 提供了几个静态方法,一行代码就能弹窗:

// 信息提示
QMessageBox::information(this, "标题", "文件保存成功!");

// 警告
QMessageBox::warning(this, "警告", "磁盘空间不足!");

// 错误
QMessageBox::critical(this, "错误", "无法打开文件!");

// 询问(带 Yes/No 按钮)
int ret = QMessageBox::question(this, "确认",
                                "确定要删除吗?",
                                QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
    // 执行删除
}

小技巧:question() 的返回值一定要判断,别漏了。我曾经见过新手直接调用 question() 但不检查返回值,结果点了「否」也照样删数据……嗯,那场面挺尴尬的。

如果你想自定义按钮文字,比如把「Yes/No」改成「保存/不保存」,可以这样:

QMessageBox msgBox;
msgBox.setWindowTitle("提示");
msgBox.setText("文档已修改,是否保存?");
msgBox.setIcon(QMessageBox::Question);

QPushButton *saveBtn = msgBox.addButton("保存", QMessageBox::AcceptRole);
QPushButton *discardBtn = msgBox.addButton("不保存", QMessageBox::DestructiveRole);
msgBox.addButton("取消", QMessageBox::RejectRole);

msgBox.exec();

if (msgBox.clickedButton() == saveBtn) {
    // 保存
} else if (msgBox.clickedButton() == discardBtn) {
    // 不保存
}

28.2 QFileDialog:文件选择器

文件对话框也是高频组件。打开文件、保存文件、选择目录,全用它。我个人习惯把常用的几种用法记在脑子里,写代码时直接敲,不用翻文档。

// 打开单个文件
QString filePath = QFileDialog::getOpenFileName(
    this,
    "选择图片",
    "C:/Users/Default/Pictures",
    "图片文件 (*.png *.jpg *.bmp);;所有文件 (*.*)"
);

// 打开多个文件
QStringList files = QFileDialog::getOpenFileNames(
    this, "选择多个文件", "C:/", "文本文件 (*.txt)"
);

// 保存文件
QString savePath = QFileDialog::getSaveFileName(
    this, "保存为", "untitled.txt", "文本文件 (*.txt)"
);

// 选择目录
QString dir = QFileDialog::getExistingDirectory(
    this, "选择文件夹", "C:/"
);

注意:getOpenFileName 返回的路径是空字符串,表示用户取消了操作。一定要判断!我曾经在项目里忘了判断,结果用户取消后程序直接崩溃了……从那以后我养成了习惯:每次调用文件对话框后,先 if(filePath.isEmpty()) return;

28.3 QColorDialog:颜色选择器

颜色对话框适合让用户选择颜色,比如绘图软件、主题设置等场景。

QColor color = QColorDialog::getColor(Qt::white, this, "选择背景色");
if (color.isValid()) {
    // 使用 color
    ui->textEdit->setTextColor(color);
}

注意 getColor 返回的 QColor 可能无效(用户点了取消),所以要用 isValid() 判断一下。嗯,这个细节容易忘。

28.4 QFontDialog:字体选择器

字体对话框让用户选择字体、字号、粗体、斜体等属性。

bool ok;
QFont font = QFontDialog::getFont(&ok, QFont("微软雅黑", 12), this);
if (ok) {
    ui->textEdit->setFont(font);
}

这里有个小坑:getFont 的第一个参数是 bool*,用来返回用户是否点了「确定」。如果用户点了取消,ok 就是 false,这时候 font 可能是个无效值,别直接用。

28.5 自定义对话框:自己动手,丰衣足食

标准对话框虽然方便,但有时候满足不了需求。比如你要做一个「用户登录」对话框,里面有用户名、密码输入框,还有「记住我」复选框——这种场景就得自己写自定义对话框。

做法很简单:

  1. 新建一个类,继承 QDialog
  2. 在构造函数里添加需要的控件(QLabel、QLineEdit、QPushButton 等)
  3. 用布局管理器(QVBoxLayout、QHBoxLayout)排好
  4. 添加信号/槽处理逻辑
  5. 调用 exec() 显示为模态对话框

来个简单的例子:

// LoginDialog.h
class LoginDialog : public QDialog {
    Q_OBJECT
public:
    explicit LoginDialog(QWidget *parent = nullptr);
    QString username() const;
    QString password() const;
private:
    QLineEdit *m_userEdit;
    QLineEdit *m_pwdEdit;
};

// LoginDialog.cpp
LoginDialog::LoginDialog(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle("用户登录");
    setFixedSize(300, 200);

    auto *layout = new QVBoxLayout(this);

    layout->addWidget(new QLabel("用户名:"));
    m_userEdit = new QLineEdit(this);
    layout->addWidget(m_userEdit);

    layout->addWidget(new QLabel("密码:"));
    m_pwdEdit = new QLineEdit(this);
    m_pwdEdit->setEchoMode(QLineEdit::Password);
    layout->addWidget(m_pwdEdit);

    auto *btnLayout = new QHBoxLayout();
    QPushButton *okBtn = new QPushButton("登录", this);
    QPushButton *cancelBtn = new QPushButton("取消", this);
    btnLayout->addWidget(okBtn);
    btnLayout->addWidget(cancelBtn);
    layout->addLayout(btnLayout);

    connect(okBtn, &QPushButton::clicked, this, &QDialog::accept);
    connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
}

QString LoginDialog::username() const { return m_userEdit->text(); }
QString LoginDialog::password() const { return m_pwdEdit->text(); }

// 使用
LoginDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
    QString user = dlg.username();
    QString pwd = dlg.password();
    // 执行登录逻辑
}

经验之谈:自定义对话框里,我习惯用 accept() 和 reject() 来关闭对话框,而不是直接 close()。因为调用 accept() 后,exec() 会返回 QDialog::Accepted,调用 reject() 返回 QDialog::Rejected,这样调用方就能知道用户点了「确定」还是「取消」。

28.6 模态 vs 非模态

对话框有两种显示方式:

  • 模态对话框:调用 exec(),阻塞程序,用户必须处理完对话框才能操作其他窗口。大部分场景用这个。
  • 非模态对话框:调用 show(),不阻塞,用户可以同时操作对话框和主窗口。适合「查找替换」这种需要反复使用的面板。

非模态对话框有个坑:记得设置 Qt::WA_DeleteOnClose 属性,否则关闭后对象不释放,内存泄漏。我刚开始用 Qt 时就踩过这个坑,调试了半天才发现对话框对象一直没删。

FindDialog *dlg = new FindDialog(this);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();

28.7 避坑总结

常见问题 原因 解决办法
对话框弹出后程序卡死 在非主线程调用了 exec() 确保在主线程(GUI线程)调用对话框
getOpenFileName 返回空路径导致崩溃 没判断用户取消的情况 加 if(path.isEmpty()) return;
自定义对话框关闭后数据拿不到 用了 close() 而不是 accept()/reject() 用 accept() 和 reject() 关闭
非模态对话框内存泄漏 没设置 WA_DeleteOnClose setAttribute(Qt::WA_DeleteOnClose)

好了,对话框这块就这些内容。标准对话框用起来简单,自定义对话框稍微多写几行代码,但灵活性高。我个人建议:能用标准对话框的就别自己造轮子,除非需求确实特殊。毕竟 Qt 团队已经帮我们踩过很多坑了,直接用更省心。

一句话总结:QMessageBox 管提示,QFileDialog 管文件,QColorDialog 管颜色,QFontDialog 管字体,QDialog 管一切自定义需求。记住这五个,对话框这块你就拿下了。


公众号:蓝海资料掘金营,微信deep3321