跳转到内容
新建笔记

Qt QAction:菜单、工具栏与选中状态

QAction 表示一项可以从菜单、工具栏或快捷键触发的命令。同一个 action 加到多个入口时,共享可用、选中和文本等状态,不必为每个入口分别维护一套开关。

本文 C++ 示例的 --self-test 使用 assert,应在 Debug 构建或未定义 NDEBUG 的情况下运行;Release 构建若禁用了断言,退出成功不代表这些检查已经执行。

三个状态与两个常用信号

跳转到“三个状态与两个常用信号”
状态或信号含义常用写法
checkable动作是否具有开/关状态;不保证每个平台都画成复选框action->setCheckable(true)
checked当前是否选中,先启用 checkableaction->setChecked(true)
enabled用户是否可以操作该动作action->setEnabled(false)
toggled(bool)选中状态发生变化,包括程序调用 setChecked同步实际开关状态
triggered(bool)动作被触发;单独 setChecked 不会产生此信号执行命令,或处理用户触发

这些信号的用途有交集,但不能因为“都带 bool”就接错。初始化时如果先设 checked、再连接 toggled,连接建立前那次变化不会补发,应直接初始化相应界面。QAction

完整菜单与共享工具栏示例

跳转到“完整菜单与共享工具栏示例”

下面合并原来的菜单片段和独立主窗口程序,保留 action1/action2 与 Toggle Action 的用途。保存为 main.cpp,使用 Qt 6 Widgets、C++17 构建,传入 --self-test 检查信号和对象关系。

#include <QApplication>
#include <QAction>
#include <QLabel>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QStyle>
#include <QToolBar>
#include <cassert>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QMainWindow window;
auto *status = new QLabel("Toggle is ON", &window);
window.setCentralWidget(status);
auto *bar = window.menuBar();
auto *regular = new QMenu("Menu: regular", &window);
auto *action1 = new QAction(window.style()->standardIcon(QStyle::SP_ArrowRight),
"action1", &window);
auto *action2 = new QAction("action2", &window);
regular->addActions({action1, action2});
bar->addMenu(regular);
regular->addSeparator();
auto *toggle = new QAction("Toggle Action", &window);
toggle->setCheckable(true);
toggle->setChecked(true);
toggle->setEnabled(true);
bar->addMenu("Options")->addAction(toggle);
auto *toolbar = window.addToolBar("Commands");
toolbar->addAction(toggle);
int stateChanges = 0;
int activations = 0;
QObject::connect(toggle, &QAction::toggled, &window, [&](bool checked) {
++stateChanges;
status->setText(checked ? "Toggle is ON" : "Toggle is OFF");
});
QObject::connect(toggle, &QAction::triggered, &window,
[&](bool) { ++activations; });
QObject::connect(action1, &QAction::triggered, &window,
[status] { status->setText("action1 triggered"); });
QObject::connect(action2, &QAction::triggered, &window,
[status] { status->setText("action2 triggered"); });
if (app.arguments().contains("--self-test")) {
assert(toggle->isCheckable() && toggle->isChecked());
assert(action1->parent() == &window);
assert(regular->actions().contains(action1));
assert(toolbar->actions().contains(toggle));
toggle->setChecked(false);
assert(stateChanges == 1 && activations == 0);
assert(status->text() == "Toggle is OFF");
toggle->trigger();
assert(toggle->isChecked() && stateChanges == 2 && activations == 1);
action1->trigger();
assert(status->text() == "action1 triggered");
toggle->setEnabled(false);
assert(!toggle->isEnabled());
return 0;
}
window.resize(460, 220);
window.show();
return app.exec();
}

原代码中的 QMenuBar::addMenu(QMenu *) 并没有在 Qt 6 被整体弃用;它仍是可用重载。此重载不会取得传入 QMenu 的所有权,所以例子给 menu 和 actions 都指定主窗口为父对象。向菜单添加一个现成 QAction 也不应被当成自动修改它的 QObject 父对象。QMenuBar::addMenu、QWidget::addAction

原片段把创建菜单放在按钮点击槽中,反复点击会反复新建菜单栏;固定菜单应在窗口初始化时创建一次。若确实需要动态菜单,就明确更新、复用或销毁旧对象。原图标路径 :/resource/image/向右箭头.jpg 需要对应资源文件;本例使用标准图标,让独立程序不依赖未给出的 qrc。

菜单栏中展开常规菜单、悬停 action1 和 action2 的原动画

动画记录的是展开菜单、移动高亮和关闭菜单的过程,不能仅从悬停高亮判定 action 的 checked 状态。实际选中指示、图标和菜单位置受平台样式影响。菜单内部的分隔线使用 QMenu::addSeparator();QMenuBar::addSeparator() 是菜单栏上的分隔 action,平台可能以不同方式呈现,不保证是一根竖线。