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

在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 的用處,想必有所瞭解了。若某天你發現你要幹自造輪子的大活,那就得這樣弄了。

 

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