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,这里我用了 恒 !等 !于 !成功找到突破口,继续向下。

QWidget跨平台原因分析

看到这里就很兴奋了,看名字就知道是跟平台相关的的一个虚类,大胆猜想这个类的存在就是支持跨平台的关键所在!动手一试,果然如此:

QWidget跨平台原因分析

这是windows平台的

QWidget跨平台原因分析

这是linux平台的:

QWidget跨平台原因分析安卓和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);

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

上一篇:c – QScrollArea与QWidget和QVBoxLayout无法正常工作


下一篇:【Qt文档阅读】Window and Dialog Widgets