QWidget跨平臺原因分析

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 &parameters,
                                       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);

這裏就已經很明瞭了,繼續向下沒啥意義了,這篇就到此爲止了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章