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);
這裏就已經很明瞭了,繼續向下沒啥意義了,這篇就到此爲止了