QMainWindow 这套布局框架,我折腾了两天才搞明白

最近在学 Qt 的窗口系统,之前几章一直用 QWidget 当主窗口,写着写着发现不对劲——菜单栏、工具栏、状态栏全得自己用布局拼,太蠢了。翻了下文档才知道 Qt 有个 QMainWindow,专门干这事的。

但说实话,刚上手的时候踩了不少坑,记录一下。

先说 QMainWindow 的结构

QMainWindow 把一个窗口分成了固定的几块区域:

  • 最上面是菜单栏(QMenuBar)
  • 菜单栏下面可以放工具栏(QToolBar),而且能放好几条
  • 中间是核心工作区(Central Widget),这个必须设置
  • 最下面是状态栏(QStatusBar)
  • 左右两边可以停靠浮动窗口(QDockWidget)

我一开始以为 QMainWindow 和 QWidget 差不多,直接往里面 addLayout 就行。结果发现它根本不让你这么干——你必须用 setCentralWidget() 设置中央控件,其他区域用专门的 API 添加。

菜单栏这块

菜单栏的层级关系是:QMenuBar → QMenu → QAction。

QMenuBar *mb = menuBar();  // QMainWindow 自带的,不用 new
QMenu *fileMenu = mb->addMenu("文件(&F)");
QAction *actOpen = fileMenu->addAction("打开(&O)");
QAction *actSave = fileMenu->addAction("保存(&S)");
fileMenu->addSeparator();  // 加条分隔线
QAction *actQuit = fileMenu->addAction("退出(&Q)");

这里有个细节:&F 这种写法是设置快捷键下划线的,按 Alt+F 就能打开文件菜单。我之前一直不知道这个语法。

然后 QAction 被点击时会发出 triggered() 信号,连接到槽函数就行:

connect(actOpen, &QAction::triggered, this, &MainWindow::onOpen);

一个 QAction 可以同时加到菜单和工具栏里,这点设计得挺好的——同一个动作不用写两遍。

工具栏

QToolBar *toolbar = addToolBar("主工具栏");
toolbar->addAction(actOpen);   // 直接复用菜单里的 QAction
toolbar->addAction(actSave);
toolbar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);

setAllowedAreas 可以限制工具栏只能停靠在哪些边。不设置的话用户可以把它拖到任意一边,有时候会很奇怪。

状态栏

状态栏是最简单的一块:

statusBar()->showMessage("已保存", 3000);  // 显示 3 秒后自动消失

第二个参数是毫秒数,传 0 或者不传就一直显示。我手贱试了一下传负数,结果也是一直显示,不会报错。

浮动窗口(Dock Widget)

这个东西我觉得挺实用的,类似 VS Code 左边的文件树面板那种效果:

QDockWidget *dock = new QDockWidget("文件列表", this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

QListWidget *list = new QListWidget(dock);
list->addItems({"main.cpp", "mainwindow.h", "mainwindow.cpp"});
dock->setWidget(list);

addDockWidget(Qt::LeftDockWidgetArea, dock);

用户可以拖动 dock 到允许的区域,也可以把它拖出来变成独立浮动窗口。关闭后想再显示,得通过菜单里的 action 来控制(QDockWidget 自带一个 toggleViewAction)。

对话框踩坑

Qt 内置了好几种对话框,都是静态方法一行调用,挺方便的。但有些地方不注意会出问题。

QFileDialog

QString path = QFileDialog::getOpenFileName(
    this, "打开文件", "",
    "文本文件 (*.txt);;所有文件 (*)");  // 注意是两个分号 ;;

我第一次写的时候用逗号分隔过滤器,结果过滤器显示成一整坨字符串,找了半天才发现要用 ;;

QMessageBox

auto ret = QMessageBox::question(this, "确认", "要保存吗?",
    QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
    // 保存逻辑
}

QColorDialog 和 QFontDialog

// 选颜色
QColor color = QColorDialog::getColor(Qt::black, this, "选颜色");
if (color.isValid()) {
    editor->setTextColor(color);
}

// 选字体
bool ok;
QFont font = QFontDialog::getFont(&ok, editor->font(), this);
if (ok) editor->setFont(font);

这俩都是返回值 + bool 判断的模式。颜色用 isValid() 判断用户是否点了取消,字体用传入的 bool* 参数。风格不统一,但习惯就好。

模态和非模态

这个概念之前没接触过:

  • 模态exec()):弹出对话框后,父窗口不能操作,必须先关掉对话框
  • 非模态show()):对话框和父窗口可以同时操作

上面那些 getOpenFileNamegetColor 之类的静态方法内部都是模态的。如果要非模态,得自己 new 一个 QDialog 然后 show():

QDialog *dlg = new QDialog(this);
dlg->setAttribute(Qt::WA_DeleteOnClose);  // 关闭时自动 delete,不然内存泄漏
dlg->show();

WA_DeleteOnClose 这个属性很重要,不设的话每次 show 都 new 一个,关了也不释放,内存就慢慢涨上去了。

最后

写了个简易文本编辑器把这些东西串起来练了一遍,菜单栏打开/保存文件,工具栏快捷操作,状态栏显示反馈,dock 放个文件列表。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐