QWidget跨平台原因分析

    xiaoxiao2022-07-13  165

    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);

    这里就已经很明了了,继续向下没啥意义了,这篇就到此为止了

    最新回复(0)