用VS Code搞Qt 6:Gui基礎型別——QGuiApplication和QWindow

2022-09-25 15:00:22

在99.996%的情況下,我們弄 Qt 應用都會使用 QApplication 類和 QWidget 類,即直接用 Widgets 庫中的元件/控制元件。為了方便開發人員自己造輪子,Qt 也提供了一套基礎的 GUI 元件。這些元件位於 Gui 庫中。

實際上,Widgets 也是在 Gui 庫上實現的,算是官方預設為咱們實現的圖形元件庫。若是我們自己也想實現一套圖形元件庫,就得從 Gui 庫入手。當然,此行為需要決心、恆心、耐心、信心、專心、勇氣、朝氣、力氣、努力、神力、洪荒之力。畢竟是一項大工程,沒有上述精神元素是很難做成的。因此,許多時候,我們壓根兒不會去弄,就算要寫個自定義的視覺化元件,那一般也是從 QWidget 派生。

儘管不怎麼用,但還得了解一下的。QGuiAppliction 類用於管理整個應用程式的訊息迴圈,初始化完各類元件後,開啟主訊息迴圈,直到迴圈結束,程式歸西。QApplication 類就是它的子類。

QWindow 類是各種視窗部件的基礎類別。它不僅指表單,也包括視窗上的控制元件什麼的。控制元件會被視為子視窗(就是沒有了標題列和邊框這些)。所以,從 QWindow 類派生既能實現表單邏輯,也能自定義控制元件。不過,咱們一般不會直接從 QWindow 類派生,而是使用以下兩個傢伙:

1、QRasterWindow :這個很好使,就是我們最最最的繪圖方式來畫控制元件的視覺化部分。Raster 是「光柵」的意思。

2、QOpenGLWindow :看名字也猜到,該類使用 OpenGL 來繪製控制元件的視覺化部分。

 

下面老周弄一個簡單的演示,實現一個 MyCustWindow 類,派生自 QRasterWindow。裡面也沒啥邏輯,就是先把視窗的背景刷成紅色,顯得異常地喜慶和活潑。然後當滑鼠移到視窗上時,會在視窗上畫一個餅……不,是一個圓,這個圓會跟著滑鼠指標走——其實,圓並不會動,只不過在滑鼠移動後不斷地將視窗重新繪製,以製造出圓會動的效果。

總體的程式碼檔案有四個,其實就兩個,C/C++都有標頭檔案,於是就四個了。

/* CMakeLists.txt */

project(MyApp LANGUAGES CXX)

find_package(Qt6 REQUIRED COMPONENTS Core Gui)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(MyApp WIN32
            app.h
            app.cpp
            MyCustWindow.h
            MyCustWindow.cpp)

target_link_libraries(MyApp PRIVATE
                     Qt6::Core
                     Qt6::Gui)

app.cpp 是寫 main 的,自定義視窗是 MyCustWindow.h 和 MyCustWindow.cpp。老周有個習慣,寫C++程式碼通常會新建個標頭檔案,把要用到的所有標頭檔案一次性弄上去,然後專案中其他地方包含這個標頭檔案就行了。比如這裡的 app.h。

#ifndef _APP_H_
#define _APP_H_

#include <QGuiApplication>
#include <QRasterWindow>
#include <QRect>
#include <QSize>
#include <QPainter>
#include <QColor>
#include <QEvent>
#include <QMouseEvent>

#endif

Qt 這廝基本就是這樣的,每個類一個標頭檔案,所以用到哪些類,直接上名字。_APP_H_ 只是個宏,為了防止多次包含此標頭檔案時被重複定義,沒有別的用途。這裡老周沒有用 #pragma once,據說這個不太通用。當然也沒說不能用,只要不報錯啥也能上。

接下來是自定義視窗類的定義。

/* MyCustWindow.h */

#include "app.h"

class MyCustWindow : public QRasterWindow {
    Q_OBJECT
public:
    explicit MyCustWindow(QWindow* parent = nullptr);
    ~MyCustWindow();
protected:
    void paintEvent(QPaintEvent *event) override;   
    bool event(QEvent *event) override;

private:
    QPoint _curr_pos;
};

用在Qt物件樹上的型別(說白了是 QObject 的直接或間接子類)都要加個宏 Q_OBJECT,訊號和 Cao 需要它。不知道是啥也沒事,照搬就行了。

這裡,_curr_pos 是私有的成員,主要用來記錄當前滑鼠指標的座標,以便在 paintEvent 中畫圖用。這個範例需要重寫兩個成員:

1、paintEvent:繪製視窗內容。

2、event:處理事件,捕捉滑鼠指標移動事件—— MouseMove。這裡其實可以用 eventFilter,也能起來相同的效果,不過,直接重寫 event 最划算。

 

下面看 event 方法的實現。

bool MyCustWindow::event(QEvent *event)
{
    // 分析一下事件型別
    if(event -> type() == QEvent::MouseMove)
    {
        QMouseEvent* msevent = dynamic_cast<QMouseEvent*>(event);
        // 拿到當前滑鼠指標的位置
        _curr_pos = msevent->pos();

        // 重新繪製視窗
        update();
        // 已處理,返回true
        return true;
    }

    // 剩下的留給基礎類別預設處理
    return QRasterWindow::event(event);
}

如果是滑鼠事件,那麼,event 引數的指標實際指向的是 QMouseEvent 物件,所以我這裡用 dynamic_cast 轉換了一下,這個運運算元雖然不太安全,但指標之間轉換比較好用。這裡其實不會有啥問題,因為如 event type 確定為 MouseMove,那麼 event 引數就是 QMouseEvent* 型別。接著,存取 pos() 獲得當前座標,賦值給 _curr_pos。最後一步是呼叫 update 方法,這個一定要呼叫,這樣才能強制視窗重新繪製,那個圓才會跟著滑鼠走。

當然,最後一行也少不了,畢竟我們這裡只關心滑鼠事件,可能還有很多事件,於是這些我們不感興趣的事件交給基礎類別處理。

return QRasterWindow::event(event);

 

下一步,我們畫視窗內容。

void MyCustWindow :: paintEvent(QPaintEvent *event)
{
    QColor bkColor(255,0,0);   // 背景色
    QColor ptColor(255,255,0);  // 跟隨滑鼠指標的顏色

    // 開始表演
    QPainter painter(this);
    // 設定預設背景色
    painter.setBackgroundMode(Qt::OpaqueMode);
    painter.setBackground(QBrush(bkColor));
    painter.setBrush(QBrush(ptColor));
    // 擦除畫布
    painter.eraseRect(0, 0, width(), height());
    // 畫圓
    painter.drawEllipse(_curr_pos, 20, 20);

    //event -> accept();
}

最後一行的 accept 方法呼叫,此處可以有也可以省略。accept 後事件就不再傳播到父物件了,不是父類別,是物件樹上的父物件。視窗算是這裡的頂層物件了,所以傳不傳上去無所謂。

這裡有坑,有不少同志說,呼叫 setBackground 方法無法設定背景,全是黑的。這裡要注意兩點:

1、setBackgroundMode 方法將模式設定為 OpaqueMode 時才能上背景,如果是 TransparentMode,表明是透明背景,此時設定不了背景色的。程式預設就是 OpaqueMode,所以 setBackgroundMode 一行可以省略。

2、設定背景色後,在繪製前一定要清空畫布——呼叫 eraseRect 方法,本範例中要擦除整個視窗的區域,所以,矩形的大小和視窗大小相同。

呼叫 drawEllipse 方法直接就可以畫圓了,我們這裡畫的不是橢圓,而是正圓,只要讓 x 和 y 軸方向上的半徑相等(都是 20)就可以了。

 

回到 app.cpp,寫 main 入口函數。

#include "app.h"
#include "MyCustWindow.h"

int main(int argc, char* argv[])
{
    QGuiApplication app(argc, argv);

    // 範例化視窗
    MyCustWindow window;
    // 標題
    window.setTitle("喜狼狼和灰太羊");
    // 大小
    window.resize(300, 270);
    // 位置
    window.setPosition(670,364);
    // 顯示視窗
    window.show();

    return app.exec();
}

app.exec 開啟主訊息迴圈,要在所有初始化工作完成再呼叫,因為它不會馬上返回,而是等訊息迴圈結束才返回。

 

末了,完工,咱們看看效果。

 

通過這個演示,大夥伴們對 QWindow 的用處,想必有所瞭解了。若某天你發現你要幹自造輪子的大活,那就得這樣弄了。