和前面的 一二三四 没有什么连贯性,也没涉及QWidget的派生类,既然是漫谈,我忍了。本文内容:QWidget的创建
起点...
看看本文的代码,是不是很失望?这么简单的一个超级入门级小程序,能有什么可看的?
#include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget w; w.show(); return a.exec(); }
真的没什么可看的么...
从构造到显示,我们的QWidget的event()函数会依次接收到下面的...这些...事件...
事件type | 事件type(对应前面的数字) | 所处代码 | 此时将调用的函数 |
15 | QEvent::Create | QWidget w; | |
203 | QEvent::WinIdChange | w.show(); | |
75 | QEvent::Polish | QStyle::polish() | |
13 | QEvent::Move | QWidget::moveEvent() | |
14 | QEvent::Resize | QWidget::resizeEvent() | |
17 | QEvent::Show | QWidget::showEvent() | |
24 | QEvent::WindowActivate | ||
99 | QEvent::ActivationChange | ||
26 | QEvent::ShowToParent | ||
74 | QEvent::PolishRequest | a.exec(); | QWidget::ensurePolished() |
77 | QEvent::UpdateRequest | QWidgetBackingStore::sync() | |
12 | QEvent::Paint | QWidget::paintEvent() |
(注:上表是Qt4.7/WinXP下的结果,不同平台下结果会稍有不同,你只需了解这点就好)
如何切入?
如何讨论这个问题,真的很头痛,找不到合适的切入点...
恩,考虑个问题吧?我们知道:
X11下 | Windows 下 | |
创建一个窗口 | XCreateWindow() | CreateWindow() |
显示一个窗口 | XMapWindow() | ShowWindow() |
自然而然,我们很容易将
- 这两个和前面的 QEvent::Create 和 QEvent::Show 对应起来
-
也很容易将他们和 QWidget w; 和 w.show(); 对应起来
但是,我要说的是,这个结论是错误的;创建和显示都是在QWidget::show()内完成的!
有什么用?
了解这些细节有什么用,我直接用不就好了,干嘛要考虑这些乱七八糟的东西呢?
不少人可能遇到过这种问题:
- 在构造函数内获取QWidget的size()和几何尺寸,获取的总是不对
恩,原因就是:构造函数结束时,窗口其实还没有创建,窗口尺寸尚未正式被设置。
废话少说,简单看看源码片段(注,代码中忽略了很多的细节,希望不会对大家造成误导):
构造函数
构造函数中,初始化了大量的参数(我们不关心这些,也不列出了)。我们只看看感兴趣的attribute和事件
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) { ... q->setAttribute(Qt::WA_PendingMoveEvent); q->setAttribute(Qt::WA_PendingResizeEvent); QEvent e(QEvent::Create); QApplication::sendEvent(q, &e); QApplication::postEvent(q, new QEvent(QEvent::PolishRequest)); }
- 两个带Pending的attribute,暂时不谈,稍后我们还会看到它。
- QEvent::Create 事件,也是我们前面列出的第一个事件!
-
QEvent::PolishRequest事件,注意,这儿采用的postEvent,要到exec()启动以后才会被处理。
show()
我们都知道,show()、hide()、setHidden()都是setVisible的马甲,所以:
- create()
void QWidget::setVisible(bool visible) { ... QWidget *pw = parentWidget(); if (!testAttribute(Qt::WA_WState_Created) && (isWindow() || pw->testAttribute(Qt::WA_WState_Created))) { create(); }
恩,QWidget::create()在这儿被调用,借助QWidgetPrivate::create_sys使得窗口被创建。此时QEvent::WinIdChange事件通过sendEvent被派发。
- ensurePolished()
// polish if necessary ensurePolished();
前面提到了,第一调用该函数时,QEvent::Polish事件将通过sendEvent被派发
- QLayout::activate()
// activate our layout before we and our children become visible if (d->layout) d->layout->activate();
这将通知所有的layout,你们目前的尺寸无效,用到是要重新生成!(额,我们这儿例子中其实没有用到layout)
注意:QLayout是顶级layout负责制,如果你对被嵌套的layout调用activate,最终调用的还是顶级layout(一级一级的向上找)的相应成员。
- adjustSize()
if (!wasResized && (isWindow() || !parentWidget()->d_func()->layout)) { if (isWindow()) { adjustSize(); if (windowState() != initialWindowState) setWindowState(initialWindowState); } else { adjustSize(); }
窗口的尺寸是在这儿被调整到合适大小的
- show_helper()
if (isWindow() || parentWidget()->isVisible()) { d->show_helper();
对于show()来说,这是最核心的东西了。
在该函数内,它会检查有无Pending的move或resize的属性。对我们这个,显然是有的。于是 QEvent::Move和QEvent::Resize事件通过sendEvent()被派发
然后派发QEvent::Show事件,随后调用QWidgetPrivate::show_sys()来显示窗口
-
派发QEvent::ShowToParent
QEvent showToParentEvent(QEvent::ShowToParent); QApplication::sendEvent(this, &showToParentEvent);
QApplication::exec()
事件循环,这部分就不介绍。先前通过postEvent()派发的事件以及系统的Spontaneous事件,此时都开始被处理...
PolishRequest
尽管不在此处调用,我们还是看一下PolishRequest将如何被处理:
-
QEvent::PolishRequest
case QEvent::PolishRequest: ensurePolished();
如果此时尚未polished,ensurePolished将:给自己发送QEvent::Polish事件,同时递归调用子类的ensurePolished()函数
-
QEvent::Polish
case QEvent::Polish: { style()->polish(this); setAttribute(Qt::WA_WState_Polished);
不过在我们这个例子中,在进入事件循环之前,ensurePolished() 已经被调用多次了。所以此处不会在派发QEvent::Polish事件。
Paint
在X11下,系统的Spontaneous事件 Expose 最终将被转换QEvent::Paint
int QApplication::x11ProcessEvent(XEvent* event) { Q_D(QApplication); ... switch (event->type) { case GraphicsExpose: case Expose: // paint event widget->translatePaintEvent(event); break;