28、Qt对话框:QMessageBox、QFileDialog、QColorDialog、QFontDialog、自定义对话框
对话框这东西,说白了就是程序跟用户「对话」的小窗口。你想想看,用户点了个「保存」,你得问「存到哪?」;用户删了数据,你得确认「你确定吗?」;用户想换个字体颜色,你得给他个调色板。这些场景,Qt 都帮我们封装好了现成的对话框类。
我个人习惯把对话框分成两类:标准对话框和自定义对话框。标准对话框就是 Qt 自带的那些,拿来就能用;自定义对话框呢,就是你觉得现成的不够用,自己动手撸一个。
核心知识点一览:
- QMessageBox:消息提示、警告、错误、询问
- QFileDialog:打开文件、保存文件、选择目录
- QColorDialog:颜色选择器
- QFontDialog:字体选择器
- 自定义对话框:继承 QDialog,自己设计界面
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 自定义对话框:自己动手,丰衣足食
标准对话框虽然方便,但有时候满足不了需求。比如你要做一个「用户登录」对话框,里面有用户名、密码输入框,还有「记住我」复选框——这种场景就得自己写自定义对话框。
做法很简单:
- 新建一个类,继承 QDialog
- 在构造函数里添加需要的控件(QLabel、QLineEdit、QPushButton 等)
- 用布局管理器(QVBoxLayout、QHBoxLayout)排好
- 添加信号/槽处理逻辑
- 调用 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 管一切自定义需求。记住这五个,对话框这块你就拿下了。