從QWindow到QWidget(Qt5)

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()/...

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