Qt是一个跨平台的C++库,目前无论是嵌入式操作系统UI开发,还是在Linux/windows PC级应用程序开发都占有非常庞大的用户群。既然说是跨平台,目前大约有两种方式,一种是以java/python为代表的解释执行,另一种是程序库的中间层实施跨平台,Qt做为C++界面库,选择的是后者。至于究竟是如何实现的正是本文所分析的。
这里选择对QWidget进行分析。至于为什么选择QWidget,而不选择其他控件,原因很简单,QWidget是Qt的基本控件之一,可以说是最重要的控件之一,基本上几乎所有的UI控件都是基于它的,对其进行分析也是理所当然的。
一、QWidget的创建
QWidget构造如下:
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
: QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
QT_TRY {
d_func()->init(parent, f);
} QT_CATCH(...) {
QWidgetExceptionCleaner::cleanup(this, d_func());
QT_RETHROW;
}
}
这里进入到 QWidgetPrivate 的init函数中去看:
注意:代码中删除了部分不重要的代码
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
Q_Q(QWidget);
Q_ASSERT(allWidgets);
if (allWidgets)
allWidgets->insert(q);
q->data = &data;
if (targetScreen >= 0) {
topData()->initialScreenIndex = targetScreen;
if (QWindow *window = q->windowHandle())
window->setScreen(QGuiApplication::screens().value(targetScreen, nullptr));
}
//默认是隐藏的
q->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea);
q->setAttribute(Qt::WA_WState_Hidden);
//give potential windows a bigger "pre-initial" size; create_sys() will give them a new size later
data.crect = parentWidget ? QRect(0,0,100,30) : QRect(0,0,640,480);
focus_next = focus_prev = q;
//这里注意下
if ((f & Qt::WindowType_Mask) == Qt::Desktop)
q->create();
else if (parentWidget)
q->setParent(parentWidget, data.window_flags);
//这里注意
if (QApplicationPrivate::testAttribute(Qt::AA_ImmediateWidgetCreation))
q->create();
QEvent e(QEvent::Create);
QApplication::sendEvent(q, &e);
QApplication::postEvent(q, new QEvent(QEvent::PolishRequest));
}
这里需要注意的是,除了是QDesktop类型的窗口和窗口属性设置为Qt::AA_ImmediateWidgetCreation的窗口,这个窗口是不会立刻被创建的,至于何时创建,后文会有叙述。这里的CreateEvent只是发出了创建事件,该类并没有处理这个事件,所以目前来看只是父类处理该事件。针对QDesktop类型的窗口和窗口属性设置为Qt::AA_ImmediateWidgetCreation的窗口,我们不做详细分析,是因为该窗口除了是立刻被创建之外,其与普通窗口并没有区别,那么普通的QWidget又该如何被创建呢?
在QWidget的生命历程中,我们唯一还能够让它显示出来的方法就是SetVisable和show。事实上,show函数调用的代码就是setvisable(true),所以我们还是只能去找SetVisable.
注意:这里删除了部分不重要代码
void QWidget::setVisible(bool visible)
{
Q_D(QWidget);
QWidget *pw = parentWidget();
if (!testAttribute(Qt::WA_WState_Created)
&& (isWindow() || pw->testAttribute(Qt::WA_WState_Created))) {
//这里又调用了熟悉的函数
create();
}
if (d->layout)
d->layout->activate();
QEvent showToParentEvent(QEvent::ShowToParent);
QApplication::sendEvent(this, &showToParentEvent);
}
QEvent hideToParentEvent(QEvent::HideToParent);
QApplication::sendEvent(this, &hideToParentEvent);
}
}
进入到QWidget::create:
void QWidget::create(WId window, bool initializeWindow, bool destroyOldWindow)
{
Q_D(QWidget);
if ((type == Qt::Widget || type == Qt::SubWindow) && !parentWidget()) {
type = Qt::Window;
flags |= Qt::Window;
}
//检查屏幕的环境变量
static const bool paintOnScreenEnv = qEnvironmentVariableIntValue("QT_ONSCREEN_PAINT") > 0;
if (paintOnScreenEnv)
setAttribute(Qt::WA_PaintOnScreen);
//注意这里
d->create_sys(window, initializeWindow, destroyOldWindow);
d->setModal_sys();
if (testAttribute(Qt::WA_SetWindowIcon))
d->setWindowIcon_sys();
if (isWindow() && !testAttribute(Qt::WA_SetWindowIcon))
d->setWindowIcon_sys();
}
}
二、QWidgetPrivate::create_sys
之前的分析显示进入到了create_sys,我们继续。
void QWindowPrivate::create(bool recursive, WId nativeHandle)
{
if (q->parent())
q->parent()->create();
//注意这里:
//如果按照正常的窗口创建过程,这里是一定会进来的,因为无论是创建QDesktop窗口还是普通
//QWidget窗口都是调用一样的调用一个默认参数的create()函数
QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
platformWindow = nativeHandle ? platformIntegration->createForeignWindow(q, nativeHandle)
: platformIntegration->createPlatformWindow(q);
QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceCreated);
QGuiApplication::sendEvent(q, &e);
}
代码中首先调用了一个对外的create,然而这里并没有什么有价值的货,继续向下,我们看到了什么?没错,createPlatformWindow !
这里的nativeHandle ===0,这里我用了 恒 !等 !于 !成功找到突破口,继续向下。
看到这里就很兴奋了,看名字就知道是跟平台相关的的一个虚类,大胆猜想这个类的存在就是支持跨平台的关键所在!动手一试,果然如此:
这是windows平台的
这是linux平台的:
安卓和BSD的已经贴出来了,还有很多,此处就不一一列举了。
三、QWindowsIntegration::createPlatformWindow
这次我选择windows的创建过程,linux的下次分析。
QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) const
{
//这里的QWindowsDesktopWindow实际上上最后还是调用到QPlatformWindow这里来,所以用不着担心太多
if (window->type() == Qt::Desktop) {
QWindowsDesktopWindow *result = new QWindowsDesktopWindow(window);
return result;
}
QWindowsWindowData requested;
requested.geometry = QHighDpi::toNativePixels(window->geometry(), window);
//注意这里
QWindowsWindowData obtained =
QWindowsWindowData::create(window, requested,
QWindowsWindow::formatWindowTitle(window->title()));
QWindowsWindow *result = createPlatformWindowHelper(window, obtained);
if (QWindowsMenuBar *menuBarToBeInstalled = QWindowsMenuBar::menuBarOf(window))
menuBarToBeInstalled->install(result);
return result;
}
进入到QWindowsWindowData::create继续查看:
QWindowsWindowData
QWindowsWindowData::create(const QWindow *w,
const QWindowsWindowData ¶meters,
const QString &title)
{
WindowCreationData creationData;
creationData.fromWindow(w, parameters.flags);
QWindowsWindowData result = creationData.create(w, parameters, title);
// Force WM_NCCALCSIZE (with wParam=1) via SWP_FRAMECHANGED for custom margin.
creationData.initialize(w, result.hwnd, !parameters.customMargins.isNull(), 1);
return result;
}
这里很简洁,没啥说的,继续向下,进入到
QWindowsWindowData result = creationData.create(w, parameters, title);
struct WindowCreationData
{
typedef QWindowsWindowData WindowData;
enum Flags { ForceChild = 0x1, ForceTopLevel = 0x2 };
WindowCreationData() : parentHandle(0), type(Qt::Widget), style(0), exStyle(0),
topLevel(false), popup(false), dialog(false),
tool(false), embedded(false), hasAlpha(false) {}
void fromWindow(const QWindow *w, const Qt::WindowFlags flags, unsigned creationFlags = 0);
inline WindowData create(const QWindow *w, const WindowData &data, QString title) const;
inline void applyWindowFlags(HWND hwnd) const;
void initialize(const QWindow *w, HWND h, bool frameChange, qreal opacityLevel) const;
Qt::WindowFlags flags;
};
点开create函数:
QWindowsWindowData
WindowCreationData::create(const QWindow *w, const WindowData &data, QString title) const
{
result.flags = flags;
const HINSTANCE appinst = (HINSTANCE)GetModuleHandle(0);
const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w);
const QRect rect = QPlatformWindow::initialGeometry(w, data.geometry, defaultWindowWidth, defaultWindowHeight);
if (title.isEmpty() && (result.flags & Qt::WindowTitleHint))
title = topLevel ? qAppName() : w->objectName();
const wchar_t *titleUtf16 = reinterpret_cast<const wchar_t *>(title.utf16());
const wchar_t *classNameUtf16 = reinterpret_cast<const wchar_t *>(windowClassName.utf16());
const QWindowCreationContextPtr context(new QWindowCreationContext(w, data.geometry, rect, data.customMargins, style, exStyle));
QWindowsContext::instance()->setWindowCreationContext(context);
result.hwnd = CreateWindowEx(exStyle, classNameUtf16, titleUtf16,
style,
context->frameX, context->frameY,
context->frameWidth, context->frameHeight,
parentHandle, NULL, appinst, NULL);
if (!result.hwnd) {
qErrnoWarning("%s: CreateWindowEx failed", __FUNCTION__);
return result;
}
}
注意这句:
const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w);
和这句:
result.hwnd = CreateWindowEx(exStyle, classNameUtf16, titleUtf16,
style,
context->frameX, context->frameY,
context->frameWidth, context->frameHeight,
parentHandle, NULL, appinst, NULL);
这里就已经很明了了,继续向下没啥意义了,这篇就到此为止了