19、Qt对话框:QMessageBox、QFileDialog、QColorDialog、QFontDialog、QInputDialog、自定义对话框
对话框这东西,说白了就是程序跟用户「搭话」的窗口。你想想看,用户要打开文件、选个颜色、改个字体,或者程序要弹个警告——这些场景都离不开对话框。
我在Qt里摸爬滚打这些年,用得最多的就是这几种标准对话框。它们封装好了,拿来即用。但有时候标准的不够用,就得自己动手做自定义对话框。今天咱们就把它们挨个捋一遍。
核心要点:Qt的对话框分为模态和非模态。模态会阻塞程序,非模态不会。大部分场景用模态就够了。
19.1 QMessageBox:消息提示与确认
QMessageBox是最常用的对话框。我刚开始写Qt程序时,几乎每个按钮点击都要弹个框确认一下。后来发现,其实没必要那么频繁,但关键操作还是得用。
它的用法很简单,静态方法一行搞定:
// 最简单的信息提示
QMessageBox::information(this, "标题", "这是一条信息");
// 警告
QMessageBox::warning(this, "警告", "操作有风险");
// 错误
QMessageBox::critical(this, "错误", "文件打开失败");
// 提问,带返回值
int ret = QMessageBox::question(this, "确认", "确定要删除吗?",
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
// 执行删除
}
嗯,这里要注意:question的返回值是StandardButton枚举。我见过有人直接用int接,然后跟0比较——那是错的。
如果你想自定义按钮,可以这样:
QMessageBox msgBox;
msgBox.setWindowTitle("保存");
msgBox.setText("文件已修改,是否保存?");
msgBox.setInformativeText("如果不保存,修改将丢失。");
msgBox.setIcon(QMessageBox::Question);
QPushButton *saveBtn = msgBox.addButton("保存", QMessageBox::AcceptRole);
QPushButton *discardBtn = msgBox.addButton("丢弃", QMessageBox::DestructiveRole);
QPushButton *cancelBtn = msgBox.addButton("取消", QMessageBox::RejectRole);
msgBox.exec();
if (msgBox.clickedButton() == saveBtn) {
// 保存逻辑
} else if (msgBox.clickedButton() == discardBtn) {
// 丢弃逻辑
}
我曾经在项目中遇到一个坑:用addButton添加按钮后,如果不指定Role,默认是ActionRole。这时候用clickedButton判断可能会出问题。所以一定要显式指定Role。
19.2 QFileDialog:文件选择与保存
文件对话框,每个桌面应用几乎都离不开。Qt的QFileDialog封装得很完善,支持本地文件和最近文件列表。
// 打开单个文件
QString filePath = QFileDialog::getOpenFileName(this, "选择文件",
"C:/", "文本文件 (*.txt);;所有文件 (*)");
if (!filePath.isEmpty()) {
// 处理文件
}
// 打开多个文件
QStringList files = QFileDialog::getOpenFileNames(this, "选择多个文件",
"C:/", "图片 (*.png *.jpg)");
// 保存文件
QString savePath = QFileDialog::getSaveFileName(this, "保存文件",
"untitled.txt", "文本文件 (*.txt)");
// 选择目录
QString dir = QFileDialog::getExistingDirectory(this, "选择目录", "C:/");
小技巧:getSaveFileName返回的路径可能包含用户输入的文件名,但扩展名不一定对。我习惯在返回后手动检查并补全扩展名。
过滤器格式是「描述 (*.ext)」,多个过滤器用两个分号隔开。注意星号和点之间没有空格,这是Qt的硬性要求。
19.3 QColorDialog:颜色选择
选颜色这个功能,做绘图软件或主题设置时特别常用。QColorDialog提供了标准的颜色选择面板。
QColor color = QColorDialog::getColor(Qt::white, this, "选择颜色");
if (color.isValid()) {
// 使用color
ui->label->setStyleSheet(QString("background-color: %1").arg(color.name()));
}
getColor的第一个参数是默认颜色。我建议传一个合理的默认值,比如白色或当前颜色。用户打开对话框看到颜色不对,体验会很差。
如果你想禁用透明度选择,可以这样:
QColorDialog dialog(Qt::red, this);
dialog.setOption(QColorDialog::NoUseAlpha);
dialog.exec();
QColor color = dialog.selectedColor();
19.4 QFontDialog:字体选择
字体对话框跟颜色对话框类似,也是标准化的选择组件。
bool ok;
QFont font = QFontDialog::getFont(&ok, QFont("微软雅黑", 12), this);
if (ok) {
// 用户点击了确定
ui->textEdit->setFont(font);
} else {
// 用户点击了取消,font是默认值
}
注意那个bool指针。它用来区分用户是点了确定还是取消。如果你不关心取消的情况,可以传nullptr。
避坑指南:我曾经在项目中遇到一个问题——getFont返回的字体在Windows上正常,在Linux上却显示异常。原因是字体名称在不同系统上不通用。解决方案是使用QFontDatabase先检查字体是否存在。
19.5 QInputDialog:简单输入
当只需要用户输入一行文本、一个数字或一个选项时,QInputDialog是最轻量的选择。
// 文本输入
bool ok;
QString text = QInputDialog::getText(this, "输入姓名", "请输入您的姓名:",
QLineEdit::Normal, "默认值", &ok);
if (ok && !text.isEmpty()) {
// 处理文本
}
// 整数输入
int value = QInputDialog::getInt(this, "设置年龄", "年龄:",
18, 0, 150, 1, &ok);
// 下拉选择
QStringList items = {"选项A", "选项B", "选项C"};
QString item = QInputDialog::getItem(this, "选择", "请选择一个选项:",
items, 0, false, &ok);
getText的第三个参数是EchoMode。输入密码时用QLineEdit::Password,普通文本用Normal。这个细节我见过有人忽略,结果密码明文显示在界面上。
19.6 自定义对话框
标准对话框虽然方便,但总有不够用的时候。比如你要做一个复杂的设置界面,或者带预览功能的对话框。这时候就得自己动手了。
自定义对话框通常继承QDialog,然后往上面放控件。我习惯用QDialogButtonBox来管理确定和取消按钮。
// MyDialog.h
class MyDialog : public QDialog {
Q_OBJECT
public:
explicit MyDialog(QWidget *parent = nullptr);
QString getName() const;
int getAge() const;
private:
QLineEdit *nameEdit;
QSpinBox *ageSpin;
QDialogButtonBox *buttonBox;
};
// MyDialog.cpp
MyDialog::MyDialog(QWidget *parent) : QDialog(parent) {
setWindowTitle("自定义对话框");
setMinimumSize(300, 200);
QVBoxLayout *layout = new QVBoxLayout(this);
QFormLayout *formLayout = new QFormLayout();
nameEdit = new QLineEdit(this);
ageSpin = new QSpinBox(this);
ageSpin->setRange(0, 150);
formLayout->addRow("姓名:", nameEdit);
formLayout->addRow("年龄:", ageSpin);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addLayout(formLayout);
layout->addWidget(buttonBox);
}
QString MyDialog::getName() const { return nameEdit->text(); }
int MyDialog::getAge() const { return ageSpin->value(); }
使用的时候:
MyDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
QString name = dlg.getName();
int age = dlg.getAge();
// 处理数据
}
设计原则:自定义对话框应该只负责收集数据,不负责业务逻辑。数据校验可以在对话框内部做,但真正的处理逻辑应该放在调用方。
我见过有人把数据库操作写在对话框里——那耦合度太高了。对话框的任务就是「问用户要数据」,然后把数据返回给调用方。至于数据怎么用,那是调用方的事。
19.7 对话框的模态与生命周期
对话框分两种:模态和非模态。模态对话框会阻塞程序的其他窗口,非模态不会。
| 类型 | 创建方式 | 特点 |
|---|---|---|
| 应用模态 | exec() 或 setModal(true) | 阻塞整个应用 |
| 窗口模态 | setWindowModality(Qt::WindowModal) | 只阻塞父窗口 |
| 非模态 | show() | 不阻塞,可同时操作其他窗口 |
非模态对话框要注意生命周期。我建议用指针创建,并设置Qt::WA_DeleteOnClose属性,这样关闭时自动释放内存。
MyDialog *dlg = new MyDialog(this);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
19.8 知识体系总览
下面这张图把本章的对话框体系梳理了一下,方便你对照理解:
标准对话框覆盖了大部分常见场景,自定义对话框则提供了无限可能。实际项目中,我通常是先用标准对话框快速实现功能,等需求复杂了再改成自定义的。
好了,对话框这块就聊到这儿。记住一点:对话框是程序和用户沟通的桥梁,设计得好,用户用得顺手;设计得差,用户会骂娘。多站在用户角度想想,你的对话框就不会太差。
公众号:蓝海资料掘金营,微信deep3321