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