Qt5下,QWidget系列從QtGui中被剝離出去,成爲單獨的QtWidget模塊。隨着Qt Quick2的引入,QtDeclarative也逐漸和QWidget系列也脫離關係。
最終:在Qt5下的GUI編程,有兩套不同的東西
-
QtWidget (使用一個被稱爲 BackingStore 的東西)
-
QtQuick (使用一個被稱爲 Scene Graph 的東西)
這兩個是什麼東西(我還真說不清)?但我們,
不妨先,從根本的QtGui中的QWindow這個東西開始看起,看看QWindow是如何配合QWidget進行工作的
QWindow
只有QWindow,不用我們熟悉的QWidget,該如何得到一個窗口呢?
例子一
跟着感覺走,我就這樣寫了,行不行?
#include <QtGui> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QWindow w; w.setGeometry(100, 100, 200, 300); w.show(); return app.exec(); }
恩,看起來沒什麼不妥,編譯運行,就可以得到一個窗口。大小可拖動,可以關閉。
還可以繼續跟着直覺走,嘗試子類化一下 QWindow
class Window:public QWindow { public: Window(QWindow *parent=0) :QWindow(parent) { } protected: bool event(QEvent *evt) { if (evt->type()==QEvent::Expose || evt->type()==QEvent::Resize) { // What can we do here ?? } return QWindow::event(evt); } };
同樣,這種風格是我們很熟悉的東西,
可是,到了這兒,我們如何繪製我們想要的東西呢?
注:同QWidget一樣,QWindow下也有
- keyPressEvent()
- resizeEvent()
- showEvent()
- ...
這堆東西,但我們這兒直接用最根本的event()函數。
例子二
在QWidget中,我們有熟悉的QPainter類,它可以在QPaintDevice上進行繪製操作。
現在,我還是想用QPainter,於是,需要認識一個新朋友:QBackingStore
#include <QtGui> class Window:public QWindow { public: Window(QWindow *parent=0) :QWindow(parent), m_backingStore(this) { } protected: bool event(QEvent *evt) { if (evt->type()==QEvent::Expose || evt->type()==QEvent::Resize) { QRect rect(QPoint(), geometry().size()); m_backingStore.resize(rect.size()); m_backingStore.beginPaint(rect); QPainter p(m_backingStore.paintDevice()); p.setBrush(Qt::blue); p.drawEllipse(rect); m_backingStore.endPaint(); m_backingStore.flush(rect); } return QWindow::event(evt); } private: QBackingStore m_backingStore; };
- 創建了一個QBackingStore的實例
- 當收到重繪(Expose)或大小改變(Resize)的事件時,進行重繪
- 將 backingStore 調整和合適的尺寸。(我們此處和窗口一樣大)
- 獲取相應的 painteDevice()。(比如後臺提供的一個QImage圖片)
- 用熟悉的QPainter進行熟悉的繪圖操作
- 繪圖...
- 將繪製的東西送入顯存。
QWidget
在Qt5下,我們仍然可以使用QWidget,和Qt4下沒有區別。
例子三
QWidget的例子沒必要列了(略)
class Widget:public QWidget { public: Widget(){} protected: void paintEvent(QPaintEvent*){...} };
只是,這個東西和前面的QWindow如何關聯上呢?
派生類?
QWidgt是QWindow的派生類麼?如果是那就簡單了,可是
class QWidget : public QObject, public QPaintDevice { ... };
在QWidget之Alien與Native小記一文中,我們知道QWidget有alien和native之分,那麼對這個結果也就不驚奇了。因爲:
- 大部分QWidget默認都是alien的,而QWindow天生就是native的。
什麼關係?
對於每一個native的QWidget的,其私有成員QWidgetPrivate中有一個:
d->extra->topextra->window = new QWidgetWindow(q);
其中,QWidgetWindow是QWindow的派生類。
這樣一來,問題就解決了:
-
EventDispatcher將從窗口系統中獲得的事件,通過Q派發到QWindow(此處即QWidgetWindow)的event()函數中。
- QWidgetWindow將這些事件稍加處理,最終大量事件將派發到對應的QWidget::event()函數中
- 注意:因爲這個QWidgetWindow所依附的native QWidget中一般會有大量的alien的QWidget,事件最終派發到某個QWidget(不一定是它依附的native QWidget)。
太亂了?
看了例子,
- 當窗口大小改變時,程序將收到窗口系統提供的RESIZE事件通知
-
EventDispatcher負責將事件派發到QWindow,此處是它的派生類QWidgetWindow
- QWidgetWindow的event()收到QEvent::Resize事件,處理該事件
-
發現某些部分dirty,需要重繪,調用QWidgetPrivate::drawWidget()向QBackingStore對應的paintDevice()中進行繪製
- drawWidget()生成QPaintEvent事件
- 事件派發到QWidget::event(),進而paintEvent()函數被調用
-
- 所有東西都繪製到QBackingStore對應的paintDevice()中,然後調用系統api,將其送入顯存。
-
在Windows下,GetDC()/!BitBlt()/ReleaseDC()
- 在Linux(xcb)下,xcb_create_gc()/xcb_image_put()/xcb_flush()/...
-