在 Qt 开发中,经常会看到这样的代码:
| |
很多人会写,但不一定说得清这三个调用分别在做什么、为什么要按这个顺序调用。
如果只看现象,很容易把它们理解成“都和把窗口弄到前台有关”。但从 Qt 源码来看,这三个函数负责的是 三个不同层面 的事情:
show():让窗口变成可见raise():调整 Z 轴顺序,让窗口尽量到上层activateWindow():请求把窗口设为活动窗口,争取键盘焦点
这三件事并不等价,所以调用顺序也不是随便写的。
1. show() :先让窗口可见
先看 QWidget::show():
| |
这里最关键的信息有两个。
1.1 show() 的本质是 setVisible(true)
对于普通子控件,show() 直接就是 setVisible(true)。
对于顶层窗口,Qt 会先看平台和窗口标志,决定是否默认以:
- 全屏
showFullScreen() - 最大化
showMaximized() - 普通可见
setVisible(true)
所以 show() 的核心职责只有一个:让窗口进入可见状态。
它不负责:
- 不负责提升层级
- 不负责抢焦点
- 不负责把最小化窗口恢复成普通状态
1.2 show() 不会主动清掉最小化/最大化状态
源码里这句注释其实非常关键:
| |
意思是:show() 不会主动调用 showNormal(),因为 Qt 不想把已有的最大化/最小化状态直接抹掉。
这意味着:如果一个窗口已经最小化,你再调 show(),它并不等于“恢复窗口”。这也是为什么很多场景下应该用 showNormal(),而不是只写 show()。
2. showNormal() :从最小化/最大化/全屏恢复
再看 QWidget::showNormal():
| |
它做了两步:
- 清掉这几个状态位:
Qt::WindowMinimizedQt::WindowMaximizedQt::WindowFullScreen
- 再
setVisible(true)
所以 showNormal() 的语义非常明确:把窗口恢复到普通状态并显示出来。
这就是为什么下面这种场景,showNormal() 比 show() 更合适:
- 主窗口被最小化到了任务栏
- 单例程序二次启动时,需要把已有窗口恢复出来
- 点击托盘图标时,希望把隐藏/最小化窗口重新拉回桌面
换句话说:
show()解决的是“看不看得见”showNormal()解决的是“是不是还卡在最小化/最大化/全屏状态里”
3. raise() :调整堆叠顺序,不等于激活窗口
QWidget::raise() 源码如下:
| |
这个函数要分成 子控件 和 顶层窗口 两部分来看。
3.1 对子控件来说,raise() 主要是在父对象的 children 列表里挪位置
这一段最说明问题:
| |
也就是说,对于非窗口控件,raise() 首先会把当前控件移动到父控件 children 列表的最后面,从而改变绘制和堆叠顺序。
后面又会:
- 必要时
create() - 重新计算区域
invalidateBackingStore(region)触发重绘
所以对子控件来说,raise() 的核心含义是:把我放到兄弟控件的最上层。
3.2 对顶层窗口来说,真正关键的是 d->raise_sys()
对于顶层窗口,关键是这句:
| |
这说明两件事:
raise()最终要落到平台层,也就是请求窗口系统提升窗口层级- 前提是窗口已经创建,也就是
Qt::WA_WState_Created为真
这就是一个非常容易忽略的点:
如果顶层窗口还没有创建,单独调用
raise()可能什么都不会发生。
而顶层窗口通常是在 show() / setVisible(true) 之后才真正进入“已创建”状态。因此从源码角度看,先 show() 再 raise() 是合理的。
3.3 raise() 不等于“获得焦点”
raise() 做的是层级调整,不是焦点激活。
即使窗口已经被放到上层,也不代表它已经成为活动窗口,更不代表它已经拿到键盘输入焦点。
所以只写:
| |
只能表达“请把这个窗口提到前面一点”,不能表达“让用户马上输入”。
4. activateWindow() :请求激活顶层窗口
再看 QWidget::activateWindow():
| |
4.1 它操作的不是当前控件本身,而是它所属的顶层窗口
源码里写的是:
| |
也就是说,即使你在一个子控件上调用 activateWindow(),最后激活的也不是这个子控件,而是 包含它的顶层窗口。
所以 activateWindow() 的语义并不是“让这个 widget 获得焦点”,而是:
让这个 widget 所在的顶层窗口成为活动窗口。
4.2 它只是调用 requestActivate(),不是强制激活
源码最后只是:
| |
注意这个名字叫 requestActivate(),不是 forceActivate()。
也就是说,Qt 能做的是向底层窗口系统发起“激活请求”,至于系统是否接受,要看平台策略。
Qt 自己的注释已经说得很清楚:
- 在 X11 上,结果依赖 Window Manager
- 如果希望窗口也被堆叠到上面,应该额外调用
raise() - 窗口必须是可见的,否则
activateWindow()没有效果 - 在 Windows 上,如果当前应用不是活动应用,系统通常不会允许你直接抢走前台焦点,只会在任务栏上提示用户
这也解释了为什么很多人觉得 activateWindow() “有时有效,有时无效”。
不是 Qt 不执行,而是 Qt 只能请求,不能绕过操作系统的前台窗口策略。
5. 为什么推荐顺序是 showNormal() / show() → raise() → activateWindow()
从上面的源码可以直接推导出推荐顺序。
5.1 第一步先让窗口可见
activateWindow() 的注释里已经明确写了:
the window must be visible, otherwise activateWindow() has no effect.
同时 activateWindow() 内部还依赖:
| |
如果窗口还没显示,windowHandle() 很可能还没准备好,调用就直接空转。
所以必须先 show() 或 showNormal()。
5.2 第二步再调整层级
raise() 的职责是把窗口提到更上层。
Qt 注释里对 activateWindow() 也专门说了:
If you want to ensure that the window is stacked on top as well you should also call raise().
也就是说,激活 和 置顶到当前堆叠前面 是两件事。
如果你只调用 activateWindow(),某些平台上未必会自动帮你把窗口提到你想要的位置;而 raise() 就是在补这个动作。
5.3 第三步再请求激活
等窗口已经:
- 处于可见状态
- 层级也尽量被抬上来
再调用 activateWindow() 去请求焦点,语义最完整。
所以推荐顺序是:
| |
如果窗口可能处于最小化状态,则更推荐:
| |
6. 总结
从 Qt 源码看,这几个 API 的职责边界非常清楚:
show():让窗口可见,但不负责恢复普通状态,也不负责焦点showNormal():清掉最小化/最大化/全屏状态,再显示raise():调整 Z 轴顺序,尽量把窗口放到更上层activateWindow():向窗口系统请求激活顶层窗口
因此:
- 想恢复最小化窗口:
showNormal() - 想把窗口提到前面:
raise() - 想请求焦点:
activateWindow() - 想完整表达“显示并尽量切到前台”:
showNormal()/show()+raise()+activateWindow()
最常用、也最符合源码语义的调用顺序就是:
| |
如果窗口本来不是最小化状态,再把 showNormal() 换成 show() 即可。
