
窗口体系
WindowFlags
使用 Qt 作为 UI 框架离不开 QWidget,Qt 作为一个跨平台框架,QWidget 具备了展示界面和响应用户事件的基本功能,但不同平台这两者的实现都是有差异的,下面探讨下 QWidget 是如何屏蔽差异以及 Qt 的窗口体系是怎样的。
刚接触 QWidget 的时候基本都停留在使用层面,对 Qt 提供的 QWidget、QDialog、QPopup等差异不太了解,后来才发现它们之间是通过 Qt 中的 WindowFlags 区分的。

图中给出了 Qt 中各种 WindowFlags 标识。对于 QDialog、QPopup、QTool 和 QToolTip 这类的标识比较容易理解,是表明了控件的基本形态,但是 QWindow 这个标识不太好理解,另外值得注意的是 QWidget 默认是不包含 QWindow 标识的,但很多表示例如 QDialog 是包含了 Window 标识的。那么到底什么样的 Widget 才算一个 Window 呢?
要理解这一点得先看看 Qt 窗口体系的基本实现:

图中大致表达了 Qt 窗口体系的层级关系。其中横向表示继承关系,纵向表示组合关系。这里以 Qt 在 iOS 平台上的封装实现举例子,其它平台是类似的。
- 首先是最底层的 QUIView,继承了 iOS 平台原生的 UIView。显然在 iOS 平台上,所有的事件都会由操作系统传递给这里的 QUIView,然后经过 Qt 的层层封装后传递到 QWidget 体系。
- 然后是 QIOSWindow 继承自 QPlatformWindow,并且包含了 QUIView 的实例,这里可以看出 QPlatformWindow 在各个平台上都会由子类来具体实现其中的接口。通过 QPlatformWindow 的抽象,Qt 屏蔽了不同操作系统底层的差异。
- 其次是 QWidgewtWindow 继承自 QWindow,这里的 QWindowPrivate 可以理解为是为了保证良好的二进制兼容特性引入的,类似于 pImpl 模式。
- 最后是 QWidgetPrivate(等价于 QWidget) 中包含了 QWidget。注意这里用了虚线箭头,表明不是所有的 QWidget 都包含了 QWidgetWindow。
现在接着回到上一个问题,从窗口层级图中可以看出,当一个 Widget 需要包含一个原生窗口时,那么这个窗口就是一个 Window。例如一个顶层窗口(没有父窗体)或者 QDialog 内部都包含了 iOS 原生窗口 QUIView,而一个 Widget 内部的子控件通常不需要创建原生窗口,这样的子 Widget 不包含 Window 标识。
window函数

QWidget 提供了 window() 接口,根据 Qt 说明,该接口会返回当前 Widget 父窗口中最近的具有 Window 标识的窗口。这也表明整个 Qwidget 体系中至少需要一个原生窗口与操作系统层面进行交互。
QWidget什么时候创建QWindow?

一开始以为 QWidget 在构造函数中就会创建原生窗口,经过调试发现不是,是在 show() 函数调用后会创建原生窗口。可以通过 windowHandle() 接口获取对应的 QWindow。
能不能手动控制创建原生窗口?

Qt 提供了 winId() 接口来强行创建原生窗口,任何 Widget 调用该接口后都会创建自己的原生窗口。但是要注意创建原生窗口会破坏 Qt 的跨平台特性。这类原生窗口的层级会高于非原生窗口的层级,这是因为 Qt 的非原生 Widget 不包含 QWindow,本质上是在父窗口上绘制上去的。如果其中一个子控件调用 winId 变成了原生窗口,那么它将不再是绘制在父窗口上,而是单独呈现在一个 UIView 上,可以理解为其它子控件都是绘制在下层窗口(UIView)上,但是变成原生窗口的子控件会单独绘制在上层的 UIView 上,这样它的同级控件都会被它遮挡住。
总结
- Qt 通过 QPlatformWindow 屏蔽了不同系统窗口的实现差异,QPlatformWindow 以上是平台无关的实现,以下是平台相关的实现。
- Qt 中不是所有 Widget 都具备 Window 标识,创建了 QWindow 以及底层原生窗口的 Widget 会具备 Window 标识。
- QWidget 是在
show()函数调用后会创建 QWindow 和底层原生窗口。 - 通过
winId()可以为任何 Widget 创建底层原生窗口,但是会破坏 Qt 窗口的跨平台特性,还会引入层级问题。
关注公众号:C++学习与探索,有惊喜哦~
1096

被折叠的 条评论
为什么被折叠?



