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