QT入门第六天:菜单栏工具栏状态栏详解 | 零基础学QT
前言
前五天我们学习了环境搭建、信号与槽、常用控件、布局管理器、对话框。今天我们来学习菜单栏、工具栏、状态栏,这三个是QMainWindow的标配。
你有没有发现,几乎所有的桌面软件都有这些东西:
- 最上面一排:文件、编辑、帮助... → 这就是菜单栏
- 菜单栏下面一排图标按钮:打开、保存、撤销... → 这就是工具栏
- 最下面一排小字:显示行数、就绪... → 这就是状态栏
比如记事本、Word、浏览器、Photoshop,都是这个结构。QT的QMainWindow已经帮我们把这些都准备好了,直接用就行。
今天我们学习:
- QMenuBar 菜单栏
- QToolBar 工具栏
- QStatusBar 状态栏
- QAction 动作(菜单和工具栏共用的概念)
一、QAction 动作
在讲菜单和工具栏之前,先搞清楚一个重要的概念:QAction(动作)。
1.1 什么是QAction
QAction就是一个"动作",它代表了用户可以执行的一个操作,比如"打开文件"、"保存"、"复制"、"粘贴"。
为什么要有QAction这个东西呢?因为同一个操作,可能出现在多个地方:
- 在菜单栏里有一个"保存"菜单项
- 在工具栏里有一个"保存"按钮
- 还有快捷键 Ctrl+S
这三个地方触发的是同一个操作,如果分别写三份代码,那就太麻烦了。
QAction就是来解决这个问题的:创建一个QAction,然后把它同时加到菜单和工具栏里,它们共用同一个triggered信号。
💡 理解:QAction就像一个"命令",菜单和工具栏按钮只是这个命令的不同入口。
1.2 QAction的常用属性
cpp
QAction *action = new QAction("保存", this);
// 设置图标
action->setIcon(QIcon(":/icons/save.png"));
// 设置快捷键
action->setShortcut(QKeySequence::Save); // 标准快捷键 Ctrl+S
// 或者自定义
action->setShortcut(QKeySequence("Ctrl+S"));
// 设置提示文字(鼠标悬停时显示)
action->setStatusTip("保存文件");
// 设置是否可选中(复选框效果)
action->setCheckable(true);
// 设置是否可用
action->setEnabled(true);
1.3 触发信号
cpp
// 点击/触发时发出triggered信号
connect(action, &QAction::triggered, this, [](){
qDebug() << "执行保存操作";
});
二、QMenuBar 菜单栏
2.1 什么是菜单栏
菜单栏就是窗口最上面那一排菜单,比如"文件"、"编辑"、"帮助"。
QMainWindow自带一个菜单栏,用menuBar()就能拿到。
2.2 基本用法
cpp
// 获取菜单栏(QMainWindow自带)
QMenuBar *menuBar = this->menuBar();
// 添加菜单
QMenu *fileMenu = menuBar->addMenu("文件(&F)");
QMenu *editMenu = menuBar->addMenu("编辑(&E)");
QMenu *helpMenu = menuBar->addMenu("帮助(&H)");
// 给菜单添加菜单项(QAction)
QAction *openAct = fileMenu->addAction("打开(&O)");
QAction *saveAct = fileMenu->addAction("保存(&S)");
fileMenu->addSeparator(); // 加分隔线
QAction *exitAct = fileMenu->addAction("退出(&X)");
// 连接信号
connect(openAct, &QAction::triggered, this, [](){
qDebug() << "打开文件";
});
connect(saveAct, &QAction::triggered, this, [](){
qDebug() << "保存文件";
});
connect(exitAct, &QAction::triggered, this, &QWidget::close);
2.3 快捷键 & 助记符
你可能注意到了,菜单名里有&F、&E这样的写法,这叫助记符。
按 Alt + F 就能打开"文件"菜单,按 Alt + E 打开"编辑"菜单。
💡 小技巧:&后面的字母就是助记符,按Alt+那个字母就能快速打开菜单。
2.4 子菜单
菜单里面还可以再套菜单,叫子菜单:
cpp
QMenu *fileMenu = menuBar->addMenu("文件");
// 新建子菜单
QMenu *newMenu = fileMenu->addMenu("新建");
newMenu->addAction("文本文档");
newMenu->addAction("图片");
newMenu->addAction("文件夹");
fileMenu->addAction("打开");
fileMenu->addAction("保存");
这样"文件"菜单里就有一个"新建"子菜单,鼠标移过去会展开。
2.5 带图标的菜单项
cpp
QAction *openAct = new QAction(QIcon(":/icons/open.png"), "打开", this);
fileMenu->addAction(openAct);
2.6 可选中的菜单项
有些菜单项是开关性质的,比如"显示工具栏"、"自动换行":
cpp
QAction *wrapAct = editMenu->addAction("自动换行");
wrapAct->setCheckable(true); // 设为可选中
wrapAct->setChecked(true); // 默认选中
connect(wrapAct, &QAction::toggled, this, [](bool checked){
qDebug() << "自动换行:" << checked;
});
注意:可选中的动作,信号用toggled(bool)而不是triggered(),因为它会告诉你当前是选中还是取消。
三、QToolBar 工具栏
3.1 什么是工具栏
工具栏就是菜单栏下面那一排图标按钮,把常用的操作放在这里,用户点一下就能用,不用去菜单里找。
3.2 基本用法
cpp
// 创建工具栏
QToolBar *toolBar = addToolBar("工具栏");
// 添加动作(和菜单共用同一个QAction)
toolBar->addAction(openAct);
toolBar->addAction(saveAct);
toolBar->addSeparator(); // 分隔线
toolBar->addAction(copyAct);
toolBar->addAction(pasteAct);
看到了吗?直接addAction把之前创建的QAction加进去就行,菜单和工具栏共用同一个动作,不用再写一遍逻辑!
3.3 工具栏的位置
工具栏默认在顶部,但用户可以拖动它,放到左边、右边、底部,甚至拖出来变成浮动窗口。
cpp
// 设置允许停靠的位置
toolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
// 设置初始位置
addToolBar(Qt::TopToolBarArea, toolBar); // 顶部
// addToolBar(Qt::LeftToolBarArea, toolBar); // 左边
// addToolBar(Qt::RightToolBarArea, toolBar); // 右边
// addToolBar(Qt::BottomToolBarArea, toolBar); // 底部
// 禁止拖动,固定位置
toolBar->setMovable(false);
3.4 图标大小
cpp
toolBar->setIconSize(QSize(32, 32)); // 设置图标大小
3.5 文字显示方式
默认只显示图标,也可以设置显示文字,或者图标+文字:
cpp
// 只显示图标(默认)
toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
// 只显示文字
toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
// 文字在图标旁边
toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
// 文字在图标下面
toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
3.6 往工具栏加其他控件
工具栏不只能加按钮,还能加其他控件,比如下拉框、输入框:
cpp
// 加一个标签
toolBar->addWidget(new QLabel("字体:"));
// 加一个下拉框
QComboBox *fontCombo = new QComboBox();
fontCombo->addItems(QStringList() << "宋体" << "微软雅黑" << "Arial");
toolBar->addWidget(fontCombo);
四、QStatusBar 状态栏
4.1 什么是状态栏
状态栏就是窗口最下面那一条,用来显示状态信息,比如"就绪"、"第3行第5列"、"已保存"之类的。
QMainWindow也自带状态栏,用statusBar()就能拿到。
4.2 显示临时消息
cpp
// 获取状态栏
QStatusBar *statusBar = this->statusBar();
// 显示消息,3秒后自动消失
statusBar->showMessage("文件已保存", 3000);
// 一直显示,直到被新消息覆盖或清除
statusBar->showMessage("就绪");
// 清除消息
statusBar->clearMessage();
4.3 永久部件
临时消息会变,但有些信息需要一直显示在状态栏上,比如当前时间、光标位置,这些叫"永久部件"。
永久部件显示在状态栏的最右边,不会被临时消息覆盖。
cpp
// 添加永久部件
QLabel *posLabel = new QLabel("行: 1 列: 1");
statusBar->addPermanentWidget(posLabel);
// 更新内容
posLabel->setText("行: 10 列: 5");
4.4 普通部件
除了永久部件,还可以加普通部件,显示在左边:
cpp
QLabel *statusLabel = new QLabel("就绪");
statusBar->addWidget(statusLabel);
4.5 进度条
状态栏里放进度条也很常见,比如下载进度:
cpp
QProgressBar *progressBar = new QProgressBar();
progressBar->setRange(0, 100);
progressBar->setValue(50);
progressBar->setFixedWidth(150);
statusBar->addPermanentWidget(progressBar);
五、综合实战:完善记事本
我们来把第五天的记事本完善一下,加上菜单栏、工具栏、状态栏。
5.1 完整代码
cpp
#include "mainwindow.h"
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QAction>
#include <QTextEdit>
#include <QFileDialog>
#include <QMessageBox>
#include <QFile>
#include <QTextStream>
#include <QLabel>
#include <QComboBox>
#include <QFontComboBox>
#include <QSpinBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle("简易记事本");
resize(800, 600);
// 文本编辑区
m_textEdit = new QTextEdit(this);
setCentralWidget(m_textEdit);
// ===== 创建动作 =====
createActions();
// ===== 创建菜单栏 =====
createMenus();
// ===== 创建工具栏 =====
createToolBars();
// ===== 创建状态栏 =====
createStatusBar();
// 初始状态
m_statusLabel->setText("就绪");
m_posLabel->setText("行: 1 列: 1");
// 监听光标位置变化
connect(m_textEdit, &QTextEdit::cursorPositionChanged,
this, &MainWindow::updateCursorPos);
// 监听文本变化,标记未保存
connect(m_textEdit, &QTextEdit::textChanged, this, [=](){
if (!windowTitle().endsWith("*")) {
setWindowTitle(windowTitle() + "*");
}
});
}
// 创建所有动作
void MainWindow::createActions()
{
// 文件操作
m_newAct = new QAction("新建(&N)", this);
m_newAct->setShortcut(QKeySequence::New);
m_newAct->setStatusTip("新建一个文件");
connect(m_newAct, &QAction::triggered, this, &MainWindow::newFile);
m_openAct = new QAction("打开(&O)...", this);
m_openAct->setShortcut(QKeySequence::Open);
m_openAct->setStatusTip("打开一个文件");
connect(m_openAct, &QAction::triggered, this, &MainWindow::openFile);
m_saveAct = new QAction("保存(&S)", this);
m_saveAct->setShortcut(QKeySequence::Save);
m_saveAct->setStatusTip("保存文件");
connect(m_saveAct, &QAction::triggered, this, &MainWindow::saveFile);
m_saveAsAct = new QAction("另存为(&A)...", this);
m_saveAsAct->setShortcut(QKeySequence::SaveAs);
m_saveAsAct->setStatusTip("另存为新文件");
connect(m_saveAsAct, &QAction::triggered, this, &MainWindow::saveAsFile);
m_exitAct = new QAction("退出(&X)", this);
m_exitAct->setShortcut(QKeySequence::Quit);
m_exitAct->setStatusTip("退出程序");
connect(m_exitAct, &QAction::triggered, this, &QWidget::close);
// 编辑操作
m_undoAct = new QAction("撤销(&U)", this);
m_undoAct->setShortcut(QKeySequence::Undo);
m_undoAct->setStatusTip("撤销上一步操作");
connect(m_undoAct, &QAction::triggered, m_textEdit, &QTextEdit::undo);
m_redoAct = new QAction("重做(&R)", this);
m_redoAct->setShortcut(QKeySequence::Redo);
m_redoAct->setStatusTip("重做刚才撤销的操作");
connect(m_redoAct, &QAction::triggered, m_textEdit, &QTextEdit::redo);
m_cutAct = new QAction("剪切(&T)", this);
m_cutAct->setShortcut(QKeySequence::Cut);
m_cutAct->setStatusTip("剪切选中的内容");
connect(m_cutAct, &QAction::triggered, m_textEdit, &QTextEdit::cut);
m_copyAct = new QAction("复制(&C)", this);
m_copyAct->setShortcut(QKeySequence::Copy);
m_copyAct->setStatusTip("复制选中的内容");
connect(m_copyAct, &QAction::triggered, m_textEdit, &QTextEdit::copy);
m_pasteAct = new QAction("粘贴(&P)", this);
m_pasteAct->setShortcut(QKeySequence::Paste);
m_pasteAct->setStatusTip("粘贴剪贴板内容");
connect(m_pasteAct, &QAction::triggered, m_textEdit, &QTextEdit::paste);
m_selectAllAct = new QAction("全选(&A)", this);
m_selectAllAct->setShortcut(QKeySequence::SelectAll);
m_selectAllAct->setStatusTip("选中所有内容");
connect(m_selectAllAct, &QAction::triggered, m_textEdit, &QTextEdit::selectAll);
// 视图
m_wrapAct = new QAction("自动换行(&W)", this);
m_wrapAct->setCheckable(true);
m_wrapAct->setChecked(true);
m_wrapAct->setStatusTip("切换自动换行");
connect(m_wrapAct, &QAction::toggled, this, [=](bool checked){
m_textEdit->setLineWrapMode(checked ? QTextEdit::WidgetWidth : QTextEdit::NoWrap);
});
// 帮助
m_aboutAct = new QAction("关于(&A)...", this);
m_aboutAct->setStatusTip("关于本软件");
connect(m_aboutAct, &QAction::triggered, this, &MainWindow::showAbout);
}
// 创建菜单栏
void MainWindow::createMenus()
{
QMenuBar *menuBar = this->menuBar();
// 文件菜单
QMenu *fileMenu = menuBar->addMenu("文件(&F)");
fileMenu->addAction(m_newAct);
fileMenu->addAction(m_openAct);
fileMenu->addAction(m_saveAct);
fileMenu->addAction(m_saveAsAct);
fileMenu->addSeparator();
fileMenu->addAction(m_exitAct);
// 编辑菜单
QMenu *editMenu = menuBar->addMenu("编辑(&E)");
editMenu->addAction(m_undoAct);
editMenu->addAction(m_redoAct);
editMenu->addSeparator();
editMenu->addAction(m_cutAct);
editMenu->addAction(m_copyAct);
editMenu->addAction(m_pasteAct);
editMenu->addSeparator();
editMenu->addAction(m_selectAllAct);
// 视图菜单
QMenu *viewMenu = menuBar->addMenu("视图(&V)");
viewMenu->addAction(m_wrapAct);
// 帮助菜单
QMenu *helpMenu = menuBar->addMenu("帮助(&H)");
helpMenu->addAction(m_aboutAct);
}
// 创建工具栏
void MainWindow::createToolBars()
{
// 文件工具栏
QToolBar *fileToolBar = addToolBar("文件");
fileToolBar->addAction(m_newAct);
fileToolBar->addAction(m_openAct);
fileToolBar->addAction(m_saveAct);
fileToolBar->setMovable(false); // 固定位置
// 编辑工具栏
QToolBar *editToolBar = addToolBar("编辑");
editToolBar->addAction(m_undoAct);
editToolBar->addAction(m_redoAct);
editToolBar->addSeparator();
editToolBar->addAction(m_cutAct);
editToolBar->addAction(m_copyAct);
editToolBar->addAction(m_pasteAct);
// 格式工具栏(加一些控件)
QToolBar *formatToolBar = addToolBar("格式");
formatToolBar->addWidget(new QLabel(" 字体:"));
QFontComboBox *fontCombo = new QFontComboBox();
formatToolBar->addWidget(fontCombo);
connect(fontCombo, &QFontComboBox::currentFontChanged,
m_textEdit, &QTextEdit::setFontFamily);
formatToolBar->addWidget(new QLabel(" 字号:"));
QSpinBox *sizeSpin = new QSpinBox();
sizeSpin->setRange(8, 72);
sizeSpin->setValue(12);
formatToolBar->addWidget(sizeSpin);
connect(sizeSpin, QOverload<int>::of(&QSpinBox::valueChanged),
m_textEdit, &QTextEdit::setFontPointSize);
}
// 创建状态栏
void MainWindow::createStatusBar()
{
QStatusBar *statusBar = this->statusBar();
// 左边:状态提示
m_statusLabel = new QLabel();
statusBar->addWidget(m_statusLabel);
// 右边:永久显示光标位置
m_posLabel = new QLabel();
statusBar->addPermanentWidget(m_posLabel);
}
// 更新光标位置
void MainWindow::updateCursorPos()
{
QTextCursor cursor = m_textEdit->textCursor();
int line = cursor.blockNumber() + 1;
int col = cursor.columnNumber() + 1;
m_posLabel->setText(QString("行: %1 列: %2").arg(line).arg(col));
}
// 新建文件
void MainWindow::newFile()
{
m_textEdit->clear();
m_currentFile.clear();
setWindowTitle("未命名 - 简易记事本");
m_statusLabel->setText("新建文件");
}
// 打开文件
void MainWindow::openFile()
{
QString fileName = QFileDialog::getOpenFileName(
this, "打开文件", "", "文本文件 (*.txt);;所有文件 (*.*)"
);
if (fileName.isEmpty()) return;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(this, "错误", "无法打开文件!");
return;
}
QTextStream in(&file);
m_textEdit->setText(in.readAll());
file.close();
m_currentFile = fileName;
setWindowTitle(fileName + " - 简易记事本");
m_statusLabel->setText("已打开:" + fileName);
}
// 保存文件
void MainWindow::saveFile()
{
if (m_currentFile.isEmpty()) {
saveAsFile();
return;
}
QFile file(m_currentFile);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(this, "错误", "无法保存文件!");
return;
}
QTextStream out(&file);
out << m_textEdit->toPlainText();
file.close();
// 去掉标题末尾的*
QString title = windowTitle();
if (title.endsWith("*")) {
title.chop(1);
setWindowTitle(title);
}
m_statusLabel->setText("已保存");
}
// 另存为
void MainWindow::saveAsFile()
{
QString fileName = QFileDialog::getSaveFileName(
this, "另存为", "未命名.txt", "文本文件 (*.txt)"
);
if (fileName.isEmpty()) return;
m_currentFile = fileName;
saveFile();
}
// 关于对话框
void MainWindow::showAbout()
{
QMessageBox::about(
this,
"关于简易记事本",
"简易记事本 v2.0\n\n"
"一个用QT写的简单记事本程序\n"
"支持菜单栏、工具栏、状态栏\n\n"
"作者:零基础学QT"
);
}
5.2 头文件
别忘了在头文件里声明这些成员变量和函数:
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class QTextEdit;
class QAction;
class QLabel;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
private slots:
void newFile();
void openFile();
void saveFile();
void saveAsFile();
void showAbout();
void updateCursorPos();
private:
void createActions();
void createMenus();
void createToolBars();
void createStatusBar();
QTextEdit *m_textEdit;
QString m_currentFile;
QLabel *m_statusLabel;
QLabel *m_posLabel;
// 动作
QAction *m_newAct;
QAction *m_openAct;
QAction *m_saveAct;
QAction *m_saveAsAct;
QAction *m_exitAct;
QAction *m_undoAct;
QAction *m_redoAct;
QAction *m_cutAct;
QAction *m_copyAct;
QAction *m_pasteAct;
QAction *m_selectAllAct;
QAction *m_wrapAct;
QAction *m_aboutAct;
};
#endif
5.3 运行效果
按 Ctrl + R 运行,你会看到一个功能完整的记事本:
- ✅ 顶部有菜单栏(文件、编辑、视图、帮助)
- ✅ 下面有三排工具栏(文件、编辑、格式)
- ✅ 底部有状态栏(左边显示状态,右边显示光标位置)
- ✅ 菜单和工具栏共用动作,点哪个都一样
- ✅ 有快捷键(Ctrl+N新建、Ctrl+O打开、Ctrl+S保存...)
- ✅ 鼠标悬停在按钮上,状态栏有提示
- ✅ 修改内容后标题加*号标记未保存
是不是有点专业软件的感觉了?
六、今日总结
今天我们学习了QMainWindow的三剑客:菜单栏、工具栏、状态栏,还有QAction这个核心概念。
知识点汇总
| 组件 | 作用 | 常用方法 |
|---|---|---|
| QAction | 动作(菜单和工具栏共用) | setIcon, setShortcut, setStatusTip, triggered/toggled |
| QMenuBar | 菜单栏 | addMenu, addAction, addSeparator |
| QMenu | 菜单 | addAction, addMenu, addSeparator |
| QToolBar | 工具栏 | addAction, addWidget, setMovable, setIconSize |
| QStatusBar | 状态栏 | showMessage, addWidget, addPermanentWidget, clearMessage |
重要概念
- ✅ QAction:一个动作可以同时加到菜单和工具栏,共用同一个信号
- ✅ 助记符 :菜单名里的
&F,按Alt+F快速打开 - ✅ 快捷键 :
setShortcut()设置,QKeySequence有很多标准快捷键 - ✅ 永久部件:状态栏右边,不会被临时消息覆盖
- ✅ 可选中动作 :
setCheckable(true),用toggled(bool)信号
经验分享
- 先创建动作,再建菜单和工具栏:统一管理,代码更清晰
- 善用标准快捷键:QKeySequence::Save、QKeySequence::Open这些,不用自己记
- 状态栏提示很重要:每个动作都加setStatusTip,用户体验更好
- 工具栏不要太挤:常用的放工具栏,不常用的放菜单里就行
- 分组清晰:相关的动作放一起,用分隔线分开
七、明日预告
明天我们将学习QT的事件处理机制。
什么是事件?比如:
- 鼠标点击、移动、滚轮 → 鼠标事件
- 键盘按下、松开 → 键盘事件
- 窗口大小改变 → 尺寸事件
- 窗口显示、隐藏 → 显示事件
- 定时器到时间了 → 定时器事件
QT是基于事件驱动的,理解事件机制非常重要。我们会学习:
- 什么是事件
- 怎么重写事件处理函数
- 事件的传递和忽略
- 事件过滤器
- 定时器事件
学会事件处理,你就能更灵活地控制程序的行为了!
📝 学习建议:菜单栏、工具栏、状态栏是做桌面软件的标配,一定要掌握。
练习建议:
- 把今天的记事本代码敲一遍
- 试试加一个"查找"功能到菜单和工具栏
- 试试在状态栏加一个字数统计
- 试试加一个"字体颜色"按钮到工具栏
掌握了这三剑客,做出来的软件就有模有样了!明天见,继续加油!💪