Qt按钮类03 QToolButton——大丙+Gemini
Qt按钮类03 QToolButton——大丙+Gemini
https://www.bilibili.com/video/BV1ff4y1C7CK
QToolButton 是 Qt 框架中专为工具栏(QToolBar)和快捷操作面板设计的高阶命令按钮控件。虽然它与 QPushButton 同属于基类 QAbstractButton 的子类,但在外观渲染机制、图文拓扑排版、内存模型以及级联菜单的托管控制上存在本质差异。
级联菜单其实就是“子菜单”(Submenu)。当点击或把鼠标悬停在某个菜单项上时,该菜单项旁边会弹出一个新的菜单,这就是级联。
1. QPushButton 与 QToolButton 对比
┌───────────────┐
│ QWidget │
└───────┬───────┘
│
┌───────┴───────┐
│QAbstractButton│
└───────┬───────┘
│
┌───────────────┴───────────────┐
│ │
┌───────┴───────┐ ┌───────┴───────┐
│ QPushButton │ │ QToolButton │
└───────────────┘ └───────────────┘
(标准重量级命令按钮) (增强型高阶工具按钮)
| 核心技术维度 | QPushButton (标准命令按钮) | QToolButton (高阶工具按钮) |
|---|---|---|
| 空间占用与外观表现 | 重量级控件。具备固定的边框边距(Margin/Padding),默认占用较大的视觉空间。 | 轻量级控件。天然支持自动悬浮属性(Auto-Raise),未触发时背景透明,极易紧凑地嵌入工具栏。 |
| 图文拓扑排版能力 | 仅支持基础的水平排版(文字在图标右侧)。若要实现“文字在图标下方”,必须编写 QSS 样式表或重写 paintEvent。 |
原生内置 Qt::ToolButtonStyle 状态机。无需任何附加代码,即可自由实现文字在图标下方、文字在图标右侧、纯文字、纯图标等全方位排版。 |
| 对象树生命周期模型 | 调用 setMenu() 后,按钮底层会自动托管该菜单的所有权(改变其 parent 并挂载到对象树上)。 |
调用 setMenu() 后,按钮完全不改变该菜单的所有权(不挂载对象树),必须由开发者显式指定父对象。 |
| 级联菜单控制流 | 菜单弹出模式固定唯一。点击按钮即以模态形式弹窗,且强行使常规的 clicked() 信号失效。 |
内置三大 ToolButtonPopupMode 策略。支持延时长按弹出、主体与箭头区分裂弹出、即时弹出,信号控制自由度极高。 |
2. 核心 API 与属性控制
QToolButton 操作的大部分基础函数(如 setText(), setIcon())均从父类 QAbstractButton 继承,自身则通过以下高阶 API 扩展了图文排版与视觉展现控制。
2.1 构造函数
// 工具按钮通常直接嵌入工具栏或通过布局管理器托管,因此构造函数仅提供父对象接口
explicit QToolButton(QWidget *parent = nullptr);
2.2 图文外观布局控制(ToolButtonStyle)
图标/文本布局
// [槽函数] 控制按钮内部图标与文本的相对排版拓扑关系
[slot] void setToolButtonStyle(Qt::ToolButtonStyle style);
// 返回当前工具按钮的图标显示模式
Qt::ToolButtonStyle toolButtonStyle() const;
布局样式枚举值 Qt::ToolButtonStyle 说明:
Qt::ToolButtonIconOnly:单图标模式。仅展示图标,彻底隐藏文本(默认状态)。Qt::ToolButtonTextOnly:单文本模式。仅展示文本,彻底隐藏图标。Qt::ToolButtonTextBesideIcon:水平并排模式。文本展示在图标的正右侧。Qt::ToolButtonTextUnderIcon:垂直并列模式。文本展示在图标的正下方(常用于功能区大图标按钮)。Qt::ToolButtonFollowStyle:跟随全局或宿主工具栏的默认样式。
2.3 自动悬浮属性(Auto-Raise)
// 配置工具按钮是否启用自动浮起属性
void setAutoRaise(bool enable);
// 获取当前按钮是否处于自动浮起状态
bool autoRaise() const;
- 运行机理:当设置为
true时,该按钮在初始状态下将处于“无边框且背景完全透明”的扁平状态。只有当鼠标光标悬停(Hover)在按钮区域上方时,系统才会动态渲染出激活的边框与背景,用以大幅降低密集工具栏的视觉疲劳度。
3. 按钮设置图标与文本的三种方式
3.1 方式一:基础多态配置(来自 QAbstractButton 继承)
normalBtn->setText("博丽灵梦");
normalBtn->setIcon(QIcon(":/imgs/reimu.png"));
normalBtn->setIconSize(QSize(50, 50));
normalBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
3.2 方式二:高内聚动作关联(DefaultAction 机制)
在大型工程中,同一个操作(如“保存”)常共存于菜单、右键、快捷栏。通过引入 QAction 进行一体化联动:
[slot] void QToolButton::setDefaultAction(QAction *action);
- 技术优势:工具按钮会自动拉取(Pull)该 Action 的文本、图标、自锁状态机(Checkable),并且将触发信号自动转接。
- 信号重定向:绑定
DefaultAction后,按钮的点击拦截应当从传统的clicked()信号转移到系统的triggered(QAction*)信号或直接在QAction内部拦截。
QAction* act = new QAction(normalBtn);
act->setIcon(QIcon(":/imgs/remilia.png"));
act->setText("蕾米莉亚·斯卡雷特");
normalBtn->setDefaultAction(act);
normalBtn->setIconSize(QSize(300, 300));
normalBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
3.3 方式三:内建矢量箭头直接渲染(ArrowType 机制)
无需任何外部图标素材,直接调用系统图形库在按钮表面绘制抗锯齿的矢量箭头(一旦启用,原设定的 QIcon 图标将被强行覆盖):
void setArrowType(Qt::ArrowType type);
箭头枚举值 Qt::ArrowType:
Qt::NoArrow:无箭头(默认状态)。Qt::UpArrow/Qt::DownArrow:自适应绘制向上 / 向下的矢量箭头。Qt::LeftArrow/Qt::RightArrow:自适应绘制向左 / 向右的矢量箭头。
4. 级联菜单管理与三种弹出方式判定
QToolButton 允许通过 setPopupMode 函数精细化控制级联菜单的触发时机。这是它与 QPushButton 在控制流上的核心差异。
4.1 菜单配置函数
// 将给定的菜单与此工具按钮相关联。
void setMenu(QMenu *menu);
4.2 弹出模式控制
// 设置弹出菜单的触发行为模式
void setPopupMode(QToolButton::ToolButtonPopupMode mode);
QToolButton::ToolButtonPopupMode 枚举定义了以下三种互斥的控制流向:
QToolButton::DelayedPopup(延时弹出模式)- 执行流表现:常规短促点击按钮,菜单不会弹出,系统正常发射
clicked()信号。只有当用户按下鼠标并长按一段时间后,内置的定时器超时,关联的菜单才会呼出。 - 核心细节:长按触发菜单弹出的那一刻,按钮本身的
clicked()信号将被拦截屏蔽,不会发射。
- 执行流表现:常规短促点击按钮,菜单不会弹出,系统正常发射
QToolButton::MenuButtonPopup(分裂箭头模式)- 执行流表现:按钮的物理表面会被划分成两个完全独立的交互区域。左侧为主体点击区,右侧会额外渲染出一个内建的下拉小箭头。
- 核心细节:两条执行流互不干扰。点击左侧主体区域,只发射
clicked()信号,绝对不弹出菜单;点击右侧小箭头,只弹出菜单,绝对不发射clicked()信号。
QToolButton::InstantPopup(即时弹出模式)- 执行流表现:按钮的常规点击动作被底层完全重定向。无论是轻点还是长按按钮的任意几何表面,菜单都会立刻、无延时地模态弹出。
- 核心细节:在这种模式下,按钮自身的常规点击响应被无条件剥夺,
clicked()信号彻底失效,永远不会被发射。
5. 示例代码
mainwindow.cpp构造函数:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 配置顶级视窗物理尺寸
this->resize(1600, 1000);
// 纯代码架构:手动构建中心承载组件
QWidget* centralWidget = new QWidget(this);
this->setCentralWidget(centralWidget);
// 实例化全局水平布局管理器
QHBoxLayout* centralLayout = new QHBoxLayout(centralWidget);
// =========================================================================
// 1. 双稳态自锁状态机(setCheckable)与基本样式
// =========================================================================
// 1.1 灵梦按钮:按下塞钱,按起来没有钱
// 规范:即使后续会加入布局,纯代码下显式指定父对象或在布局后交接是好习惯
QToolButton* reimuBtn = new QToolButton(this);
reimuBtn->setText("博丽灵梦");
reimuBtn->setIcon(QIcon(":/touhou/reimu.png"));
reimuBtn->setIconSize(QSize(100, 100));
reimuBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); // 文字在图标下方
reimuBtn->setCheckable(true); // 开启双稳态
// 现代 C++ 规范:显式捕获 this,避免 C++20 中 [=] 隐式捕获 this 的警告
connect(reimuBtn, &QToolButton::toggled, this, [this](bool checked)
{
if(checked) qDebug() << "【博丽神社】: 塞钱箱收到香油钱!灵梦正在认真退治妖怪中...";
else qDebug() << "【博丽神社】: 没有香油钱,灵梦继续喝茶无视异变。";
});
// 1.2 魔理沙按钮:按下DAZE
QToolButton* marisaBtn = new QToolButton(this);
marisaBtn->setText("雾雨魔理沙");
marisaBtn->setIcon(QIcon(":/touhou/marisa.png"));
marisaBtn->setIconSize(QSize(100, 100));
marisaBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); // 文字在图标右侧
connect(marisaBtn, &QToolButton::clicked, this, [this]()
{
qDebug() << "【魔理沙】: Master Spark 全功率释放 DA☆ZE!";
});
// =========================================================================
// 2. DefaultAction 联动托管机制(Action 状态同步驱动按钮外观)
// =========================================================================
// 2.1 妖梦按钮:全面托管给 QAction
QToolButton* youmuBtn = new QToolButton();
youmuBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
// 实例化全局动作流,指定默认按钮文本,显式挂载到 this 上防泄漏
QAction* actYoumu = new QAction(QIcon(":/touhou/youmu.png"), "执行冥界庭院修剪任务", this);
actYoumu->setCheckable(true); // 让 Action 本身具备双稳态自锁属性,而不是ToolButton本身
// 【核心订正】:通过 setDefaultAction 托管后,按钮的外观、状态完全同步该 Action
youmuBtn->setDefaultAction(actYoumu);
youmuBtn->setIconSize(QSize(100, 100)); // 注意:Size 仍需由 Button 自身控制
// 错误:connect(youmuBtn, &QToolButton::toggled, this, [actYoumu](bool checked)
// 【架构纠正】:被托管后,应当监听 QAction 的信号,而不是 QToolButton 的信号!
// 这样能确保无论是点击按钮,还是通过快捷键触发该 Action,逻辑都能完美收拢。
connect(actYoumu, &QAction::toggled, this, [actYoumu](bool checked)
{
if(checked)
{
qDebug() << "【白玉楼命令拦截】: 妖梦拔出楼观剑与白楼剑!";
actYoumu->setText("『人妖名剑·现世斩』!");
}
else
actYoumu->setText("执行冥界庭院修剪任务");
});
// =========================================================================
// 3. MenuButtonPopup(分裂箭头模式)与信号解耦测试
// =========================================================================
// 3. 红魔馆按钮
QToolButton* hongmoguanBtn = new QToolButton();
hongmoguanBtn->setText("红魔馆势力范围");
hongmoguanBtn->setIcon(QIcon(":/touhou/hongmoguan.png"));
hongmoguanBtn->setIconSize(QSize(200, 120));
hongmoguanBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
// 实例化 QMenu,传入 this 挂载到窗口对象树上,防止内存泄漏
QMenu* menu = new QMenu(this);
QAction *actSakuya = menu->addAction(QIcon(":/touhou/sakuya.png"), "十六夜咲夜");
QAction* actRemilia = menu->addAction(QIcon(":/touhou/remilia.png"), "蕾米莉亚·斯卡蕾特");
QAction *actFlandre = menu->addAction(QIcon(":/touhou/flandre.png"), "芙兰朵露·斯卡蕾特");
hongmoguanBtn->setMenu(menu);
hongmoguanBtn->setPopupMode(QToolButton::MenuButtonPopup); // 分裂箭头模式
// 分裂箭头模式下特性:点击左侧主体发射 clicked 信号;点击右侧小箭头弹出菜单
connect(hongmoguanBtn, &QToolButton::clicked, this, [=]()
{
qDebug() << "【红魔馆正门】: 触发了主体点击!红美铃在门前打瞌睡被抓到了。";
});
// 拦截菜单内不同 Action 的触发信号(也可以通过 menu->triggered 统一收拢)
connect(actSakuya, &QAction::triggered, this, [=]()
{
qDebug() << "【完美与潇洒的女仆】: 『幻葬·夜雾幻影』!时间停止流动,并丢出了一堆飞刀。";
});
connect(actRemilia, &QAction::triggered, this, [=]()
{
qDebug() << "【红魔之王】: 命运的红线已被操纵。神枪『冈格尼尔』(Gungnir) 蓄势待发。";
});
connect(actFlandre, &QAction::triggered, this, [=]()
{
qDebug() << "【恶魔之妹】: 破坏之眼已被捕获!『莱瓦汀』(Lævateinn) 准备进行疯狂的弹幕游戏。";
});
// =========================================================================
// 4. InstantPopup(即时弹出模式,点击主体直接出菜单,不发射 clicked 信号)
// =========================================================================
// 4.1 幽幽子按钮
QToolButton *yuyukoBtn = new QToolButton();
yuyukoBtn->setText("西行寺幽幽子");
yuyukoBtn->setIcon(QIcon(":/touhou/yuyuko.png"));
yuyukoBtn->setIconSize(QSize(100, 100));
yuyukoBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
QMenu *ghostMenu = new QMenu(this);
QAction *actEat = ghostMenu->addAction("吃光白玉楼的三色团子");
QAction *actDead = ghostMenu->addAction("引发西行妖开花(反魂蝶)");
yuyukoBtn->setMenu(ghostMenu);
yuyukoBtn->setPopupMode(QToolButton::InstantPopup); // 即时弹出模式
// 注意:在 InstantPopup 模式下,连接 clicked 信号是无效的,因为点击主体会直接弹菜单
connect(yuyukoBtn, &QToolButton::clicked, this, [=]() {
qDebug() << "【⚠️ 警告】: 这行代码永远不会执行,因为 InstantPopup 拦截并屏蔽了 clicked 信号!";
});
connect(actEat, &QAction::triggered, this, [=]()
{
qDebug() << "【白玉楼日常】: 幽幽子大人吃光了所有的点心,妖梦又要去采购了。";
});
connect(actDead, &QAction::triggered, this, [=]()
{
qDebug() << "【西行妖下】: 『死蝶浮月』... 华丽的弹幕之中隐藏着冥界主宰死生程度的能力。";
});
// =========================================================================
// 布局与内存托管
// =========================================================================
centralLayout->addStretch();
centralLayout->addWidget(reimuBtn);
centralLayout->addWidget(marisaBtn);
centralLayout->addWidget(youmuBtn);
centralLayout->addWidget(hongmoguanBtn);
centralLayout->addWidget(yuyukoBtn);
centralLayout->addStretch();
}

浙公网安备 33010602011771号