QT|QWidget的parentWidget、window、nativeParentWidget区别与理解

1. parentWidget 这个没啥好说的,就是父窗体对象。
2.window 在Qt的Qt::WindowFlags枚举中有个Qt::Window值,官方对其解释如下:
Indicates that the widget is a window, usually with a window system frame and a title bar, irrespective of whether the widget has a parent or not. Note that it is not possible to unset this flag if the widget does not have a parent.
翻译为中文意思为:指示该窗体是一个有系统边框和标题栏的窗体,即使这个窗体有父(即作为子窗体嵌入其它窗体)依然也存在边框和标题栏。
附录:
关于Qt::WindowType、Qt::WidgetAttribute各个标志含义请参见
《Qt::WindowType、Qt::WidgetAttribute各个标志含义汇总》。
QWidget::window()的官方解释如下:
Returns the window for this widget, i.e. the next ancestor widget that has (or could have) a window-system frame.
【QT|QWidget的parentWidget、window、nativeParentWidget区别与理解】If the widget is a window, the widget itself is returned.
中文意思是:
为本窗体部件返回具有window属性的窗体。例如:返回本窗体继承链向上且距离本窗体最近的祖先类窗体且具有(或可能具有)系统边框的窗体。
Qt的QWidget::isWindow()函数用来判断一个窗体是否是一个window,其官方的解释如窗体下:
Returns true if the widget is an independent window, otherwise returns false.
A window is a widget that isn't visually the child of any other widget and that usually has a frame and a window title.
A window can have a parent widget. It will then be grouped with its parent and deleted when the parent is deleted, minimized when the parent is minimized etc. If supported by the window manager, it will also have a common taskbar entry with its parent.
中文大意是:
如果这个窗体是一个独立的window,则返回true,否则返回false。一个具有window属性的窗体是这样的一个窗体:它从视觉上来说不像是其它任何窗体部件的子窗体,它通常有标题栏和系统边框。一个window可以有父窗体。window窗体经常被它的父窗体进行分组管理,当它的父窗体被删除的时候,它也会被删除,它的父窗体最小化时,它也跟着最小化了,如果它能够被窗体管理器管理,在任务栏和它的父公用一个任务栏图标。这种行为可以通过 Qt::Window枚举来指定。
上面说的可能大家还很晕,直接上如下代码说明:

QtWidgetsApplication1::QtWidgetsApplication1(QWidget *parent) : QWidget(parent) { ui.setupUi(this); setWindowTitle("parent"); QWidget* p = new QWidget(); p->setWindowTitle("windows"); p->setWindowFlag(Qt::Window); p->show(); }

QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

在代码11行通过设置窗体为Qt::Window,则将该窗体变为window窗体了。将上面的QWidget* p = new QWidget(); 改为QWidget* p = new QWidget(this); 即将this作为其父,依然是弹出上面的窗体,即不会像一般的QWidget一旦有父,就没有标题栏和边框而嵌入到父窗体。这就是上面提到的“它从视觉上来说不像是其它任何窗体部件的子窗体”。即从视觉上来说好像是一个单独、独立的带标题栏和边框的窗体 。
3.nativeParentWidget QWidget::nativeParentWidget()的官方解释如下:
Returns the native parent for this widget, i.e. the next ancestor widget that has a system identifier, or nullptr if it does not have any native parent.
中文意思是:
返回本窗体的native parent 窗体。例如:返回本窗体继承链向上且距离本窗体最近的祖先类具有系统标识符(即类似句柄的东西)的窗体,如果这样的窗体不存在,则返回nullptr。
4.这三个窗体的区别详解 4.1 预备知识:spy++工具说明 Spy++ (SPYXX.EXE) 是一个基于 Win32 的实用工具,它提供系统的进程、线程、窗口和窗口消息的图形视图。使用 Spy++ 可以执行下列操作: 显示系统对象(包括进程、线程和窗口)之间关系的图形树。 搜索指定的窗口、线程、进程或消息。 查看选定的窗口、线程、进程或消息的属性。直接从视图中选择窗口、线程、进程或消息。 通过鼠标定位,使用查找程序工具选择窗口。 使用复杂的消息日志选择参数设置消息选项。 提示使用 Spy++ 时,在许多实例中都可以单击鼠标右键显示常用命令的弹出式菜单。命令是否可用取决于指针的位置。例如,如果在指向窗口视图中的某项时单击并且选定的窗口可见,则弹出式“突出显示”菜单项将导致选定窗口的边框闪烁,从而可以轻松地在屏幕上找到该窗口。Spy++ 是一款强有力的编程辅助工具,对分析窗体结构有独到之处。可以以十六进制和十进制两种不同方式显示窗口句柄等所有数值。
Spy++的具体用法,请网上搜索,不再赘述。如果装了visual studio 2017或以上版本,在visual studio的“工具”菜单,可以找到该工具,界面如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

4.2 代码走起
#include "Widget.h" #include Widget::Widget(const QString& name, QWidget* parent) : QWidget(parent) { ui.setupUi(this); setWindowTitle("99999999999999999999999999999999"); setProperty("name", name); }

#include "Widget.h" #include #include #include int main(int argc, char *argv[]) { QApplication app(argc, argv); //a.setAttribute(Qt::AA_NativeWindows); Widget *pA = new Widget("A"); Widget* pB = new Widget("B", pA); /* pB->setWindowFlag(Qt::Window); pB->setWindowTitle("BBBBBBBBBBBBBBBBBBBBB"); pB->show(); */ Widget* pC = new Widget("C", pB); Widget* pD = new Widget("D", pC); Widget* pE = new Widget("E", pD); pA->show(); std::vector vtWidget{ pA,pB, pC, pD, pE }; for (auto wnd : vtWidget) { qDebug() << "name:" << wnd->property("name").toString(); if (wnd->parentWidget()) qDebug() << "parent:" << wnd->parentWidget()->property("name").toString(); if (wnd->nativeParentWidget()) qDebug() << "nativeParent:" << wnd->nativeParentWidget()->property("name").toString(); qDebug() << "window:" << wnd->window()->property("name").toString(); }return app.exec(); }

说明:
请将窗体标题栏字符串设置得尽可能长些,因为后续要利用spy++工具根据标题栏知道是本窗体,因为spy++探测到的窗体很多,如果标题栏设置的很短,你很难找到上面代码生成的窗体。运行上面代码,打印出如下结果:
name: "A" window: "A" name: "B" parent: "A" nativeParent: "A" window: "A" name: "C" parent: "B" nativeParent: "A" window: "A" name: "D" parent: "C" nativeParent: "A" window: "A" name: "E" parent: "D" nativeParent: "A" window: "A"

列成表格看,更直观,如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

表格1
parentWidget很好理解,就是普通的父窗体对象。根据上面对QWidget::isWindow()和QWidget::window()的解释,我们知道QWidget::window()返回的是必须同时都满足下面条件的窗体:
  • 有window属性窗体的典型特征是必须具有系统边框和标题栏。
  • 如果窗体本身就是一个window属性的窗体,则对自己调用QWidget::window()则返回自己。
  • 如果窗体本身不是一个window属性的窗体,则往本窗体的继承链上方查找具有window属性的窗体,如果找到,就返回该窗体,即返回继承链上方离自己最近的具有window属性的祖先窗体。
根据上面对QWidget::nativeParentWidget的解释,我们知道QWidget::nativeParentWidget()返回的是必须同时都满足下面条件的窗体:
  • 该窗体必须位于本窗体继承链的上方。
  • 该窗体必须是本窗体的父或祖先。
  • 该窗体必须有一个系统分配的标识符,类似id、句柄之类的东西。
从下面的代码:
Widget* pB = new Widget("B", pA); Widget* pC = new Widget("C", pB); Widget* pD = new Widget("D", pC); Widget* pE = new Widget("E", pD);

可以看出B、C、D、E窗体都作为子窗体嵌入到了某个窗体中,且没通过
setWindowFlag(Qt::Window);

设置其具有Qt::Window属性。故对B、C、D、E窗体来说,它们的继承链向上方向满足前文关于window属性窗体三个条件的窗体只有A,故B、C、D、E窗体的window()返回的是A。当将上面代码的第11、12、13行注释的代码取消注释,即将B设置为具有window属性的窗体,则输出如下:
name: "A" window: "A" name: "B" parent: "A" nativeParent: "A" window: "B" name: "C" parent: "B" nativeParent: "B" window: "B" name: "D" parent: "C" nativeParent: "B" window: "B" name: "E" parent: "D" nativeParent: "B" window: "B"

列成表格看,更直观,如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

表格2
可以看到,当将B设置为具有window属性的窗体后,对于C、D、E窗体来说,满足前文关于window属性三个条件的窗体就是B而不再是A,故C、D、E窗体的window()返回的窗体是B。且因为B能独立以带边框的窗体显示,如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

(注意:刚显示时,标题为9999....的窗体挡住了B窗体,需要将它们移开才能看见)
注意:当本对象是window属性窗体,则本身调用window()返回的窗体是本对象自己,从上面A的window()和之后B的window()就可以看出这点。
将上面代码第11、12、13行注释,弹出窗体,按照前文所述从visual studio打开spy++,按如下所示操作:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片


可以看到这里只有一个005C09F2(即系统分配的标识符,每次运行,该标识符会变)的窗体。B、C、D、E窗体的QWidget::nativeParentWidget()返回的窗体满足前文对nativeParentWidge的三个条件,故005C09F2的窗体就是B、C、D、E窗体的QWidget::nativeParentWidget()返回的窗体。A的父、祖先不存在,所以A的nativeParentWidget()返回nullptr。
取消11、12、13行注释,再次利用spy++查看,如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

可以看到这里只有2个带标识符的窗体,标识符分别为00780AD8、002E0C2E(即系统分配的标识符,每次运行,该标识符会变)的窗体,根据前文对nativeParentWidge满足的三个条件,我们知道C、D、E窗体的QWidget::nativeParentWidget()返回的是00780AD8标识符即B窗体,B窗体的QWidget::nativeParentWidget()返回的是002E0C2E标识符即A窗体。A的父、祖先不存在,所以A的nativeParentWidget()返回nullptr。
将上面的代码改为如下:
#include "Widget.h" #include #include #include int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setAttribute(Qt::AA_NativeWindows); Widget *pA = new Widget("A"); Widget* pB = new Widget("B", pA); Widget* pC = new Widget("C", pB); Widget* pD = new Widget("D", pC); Widget* pE = new Widget("E", pD); pA->show(); std::vector vtWidget{ pA,pB, pC, pD, pE }; for (auto wnd : vtWidget) { qDebug() << "name:" << wnd->property("name").toString(); if (wnd->parentWidget()) qDebug() << "parent:" << wnd->parentWidget()->property("name").toString(); if (wnd->nativeParentWidget()) qDebug() << "nativeParent:" << wnd->nativeParentWidget()->property("name").toString(); qDebug() << "window:" << wnd->window()->property("name").toString(); }return app.exec(); }

即注释或删除原来的11、12、13行代码,再在构建A之前加入如下代码:
app.setAttribute(Qt::AA_NativeWindows);

运行结果如下:
name: "A" window: "A" name: "B" parent: "A" nativeParent: "A" window: "A" name: "C" parent: "B" nativeParent: "B" window: "A" name: "D" parent: "C" nativeParent: "C" window: "A" name: "E" parent: "D" nativeParent: "D" window: "A"

列成表格看,更直观,如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

表格3
再用spy++探测,如下:
QT|QWidget的parentWidget、window、nativeParentWidget区别与理解
文章图片

同样根据上面的分析,我们就不难理解表格3中的nativeParentWidget、window列的结果了

    推荐阅读