原來的水文標題是“用 VS Code 搞 Qt6”,想想還是直接改為“Qt6”,反正這個用不用 VS Code 也能搞,雖然我知道大伙伴們都很討厭 CMake,但畢竟這廝幾乎成了 C++ 的玩家規范了,Qt 也算識大體,支持用 CMake 來構建程式,所以,只要你用的是能寫 C++ 的工具,理論上都能搞 Qt,
創建應用程式界面的時候,我們一般會選用 QWidget 以及其子類的,不過,在 Gui 模塊中,有一個 QWindow 類,干嗎用的呢?寫個程式試試看,
#include <QGuiApplication> #include <QWindow> int main(int argc, char** argv) { // 一定要先創建應用程式物件 QGuiApplication app(argc, argv); // 創建視窗實體 QWindow win; // create方法其實可以不呼叫 win.create(); // 調整視窗的大小 win.resize(300, 250); // 設定標題欄文本 win.setTitle("番薯聯盟"); // 顯示視窗 win.show(); // exec進入事件(訊息)回圈 return QGuiApplication::exec(); }
這里說明一下,QWindow 類有個 create 方法,它的作用是創建平臺相關的資源的,對應的是 destroy 方法,用來銷毀這些平臺相關的資源,這些平臺相關的資源是為了實作跨平臺的型別,如 QPlatformWindow、QPlatformSurfaceEvent 之類的,Windows 平臺有單獨的實作,Linux 平臺也單獨地實作,像 qwindowsguieventdispatcher、qunixeventdispatcher 這些也是,總之,QWindow 類可能會用到它們,于是,這些平臺相關的資源,其生命周期始于 create 方法,終于 destroy 方法,
不過,create 方法這里其實可以不呼叫的,因為 show 方法會呼叫;destroy 方法也不可以不呼叫,它在 QWindow 類的解構式中被呼叫,
咱們為上述代碼寫一個 CMakeLists.txt,
cmake_minimum_required(VERSION 3.0.0) project(TestApp VERSION 0.1.0) find_package(Qt6 REQUIRED COMPONENTS Core Gui) add_executable(TestApp main.cpp) target_link_libraries(TestApp PRIVATE Qt6::Core Qt6::Gui)
這里我們不到“鐵三角”庫,只用 core 和 gui 就夠了,不需要 widgets,
好了,嘗試運行,看看會出現什么,

這標題欄上的字體好像有問題,不管它,繼續,
哦,直接實體化 QWindow 類會呈現一個空白視窗,而且這個視窗很詭異,你拖動一下改變它的大小后,就會變成這樣,

這是因為這個視窗是真的很空,空到連基本的繪制都沒有,只是在啟動的時候填充了個顏色,這個顏色是跟隨系統主題的,剛才你看到的是深色主題下的背景色,現在我把系統主題調成淺色主題,它就會變成這樣,

當你調整其大小后,發生重新繪制的部分變成了黑色(就是啥也沒有),
QWindow 類雖然定義了 paintEvent 方法,但是,它實作了個寂寞,
void QWindow::paintEvent(QPaintEvent *ev) { ev->ignore(); }
從源代碼中你會看到,默認的實作是直接把 paint 事件忽略了,
所以,我們只能從 QWindow 類派生,并重寫 paintEvent 方法,繪制我們所需要的內容,
cmake_minimum_required(VERSION 3.0.0) project(TestApp VERSION 1.2.3) find_package(Qt6 REQUIRED COMPONENTS Core Gui) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(TestApp MyWindow.h MyWindow.cpp main.cpp) target_link_libraries(TestApp PRIVATE Qt6::Core Qt6::Gui)
1 #include <QWindow> 2 #include <QPaintEvent> 3 #include <QBackingStore> 4 5 #ifndef __MYWINDOW_H__ 6 #define __MYWINDOW_H__ 7 class MyWindow : public QWindow 8 { 9 Q_OBJECT 10 public: 11 // 建構式 12 explicit MyWindow(QWindow* parent = nullptr); 13 protected: 14 // 重寫事件 15 void paintEvent(QPaintEvent *ev) override; 16 private: 17 // 繪制視窗內容需要這個類 18 QBackingStore* m_backstore; 19 }; 20 #endif
要在視窗上涂鴉,需要用到 QBackingStore 類,這是由于 QPainter 類需要一個 QPaintDevice 指標才能完成繪圖,QBackingStore類可以通過 paintDevice 方法回傳一個 QPaintDevice 類的指標,
m_backstore 成員也可以用 QScopedPointer 封裝,防止記憶體泄漏,
private: // 繪制視窗內容需要這個類 QScopedPointer<QBackingStore> m_backstore;
當超出成員作用域時會自動洗掉指標,
下面是實作代碼,
#include "MyWindow.h" #include <QPaintDevice> #include <QPainter> #include <QColor> #include <QRect> #include <QtDebug> MyWindow::MyWindow(QWindow* parent) : QWindow(parent), m_backstore(new QBackingStore(this)) { // 設定當前視窗的位置和大小 setGeometry(799, 304, 425, 385); // 設定繪畫設備畫布大小 m_backstore -> resize(QSize(400, 300)); // 設定視窗標題 setTitle("紅紅火火"); } void MyWindow::paintEvent(QPaintEvent* ev) { // 要進行繪圖的區域 QRect rect = ev->rect(); // 開始 m_backstore->beginPaint(rect); QPaintDevice* dev = m_backstore -> paintDevice(); // 創建painter實體 QPainter painter; painter.begin(dev); // 填充矩形 painter.fillRect(rect, QColor("red")); painter.end(); // 結束 m_backstore->endPaint(); // 把繪圖輸出到視窗上 m_backstore->flush(rect); }
QBackingStore 類的建構式需要一個 QWindow 類或子類的指標,一般是當前視窗類,這里注意的是,QBackingStore 物件不能使用默認大小(程式會閃退),一定要呼叫 resize 方法設定畫布的大小(或者說你能看到的視窗大小),
當視窗需要繪制時會引發 paint 事件,重寫 paintEvent 方法自行繪制視窗內容,在上面代碼中,只是簡單的矩形填充(填充為紅色),
m_backstore->beginPaint(rect); QPaintDevice* dev = m_backstore -> paintDevice(); // 創建painter實體 QPainter painter; painter.begin(dev); // 填充矩形 painter.fillRect(rect, QColor("red")); painter.end(); // 結束 m_backstore->endPaint(); // 把繪圖輸出到視窗上 m_backstore->flush(rect);
QBackingStore.paintDevice 方法所回傳的 QPaintDevice 指標只在 beginPaint 和 endPaint 方法之間有效,QPaintDevice 是一個虛擬設備,用于構建二維坐標空間,然后才能在上面繪圖,繪圖用到 QPainter 類,這個類在實體化后,呼叫 begin 方法開始繪圖,前面獲取的 QPaintDevice 指標就在這里傳遞,繪制完后呼叫 end 方法結束,如果實體化 QPainter 類時向建構式傳遞了 QPaintDevice 指標,那就不需要呼叫 begin 方法了,
QPainter painter(dev); //painter.begin(dev); // 填充矩形 painter.fillRect(rect, QColor("red")); painter.end();
最后,main 函式中實體化 MyWindow,并顯示它,
int main(int argc, char** argv) { // 一定要先創建應用程式物件 QGuiApplication app(argc, argv); // 創建視窗實體 MyWindow win; // 顯示視窗 win.show(); // exec進入事件(訊息)回圈 return QGuiApplication::exec(); }
運行程式,看到紅紅的一塊,就說明通正確運行了,

當然,這個視窗還是有問題的,由于 QBackingStore 物件的畫布大小是硬編碼的,當調整了視窗大小后,紅色矩形只能看到一部分,沒看到的那部分仍然是黑乎乎的,
為了完善一下,我們還要重寫 resizeEvent 函式,在視窗的大小被調整后,手動修改 QBackingStore 的畫布大小,
class MyWindow : public QWindow { Q_OBJECT …… protected: …… // 調整視窗大小后發生 void resizeEvent(QResizeEvent* ev) override; …… };
void MyWindow::resizeEvent(QResizeEvent *ev) { this->m_backstore->resize(ev->size()); }
這樣處理之后,視窗的背景色就能正常繪制了,哪怕你調整了視窗大小,

直接從 QWindow 類繼承還是不太方便的,內部還要使用 QBackingStore 類,于是,我們可以考慮用 QWindow 的派生類,比如 QRasterWindow,這個類是用于創建基于像素呈現的視窗——相對應的是 QOpenGLWindow,兩者用法差不多,只是繪制方式不同罷了,
QRasterWindow和QOpenGLWindow類都是 QPaintDevice 和 QWindow 的子類,所以從 QRasterWindow 派生的自定義視窗不需要定義 QBackingStore成員了,視窗自身的實體就可以傳遞給 QPainter 物件,
接下來咱們演示一下,先編好 CMakeLists.txt 檔案,
cmake_minimum_required(VERSION 3.8) project(HelloApp VERSION 1.0.0 LANGUAGES CXX) # Qt內褲包 find_package(Qt6 REQUIRED COMPONENTS Core Gui) # 開啟MOC等選項 set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_AUTOMOC YES) # 代碼目錄 file(GLOB SRCS src/*.cpp includes/*.h) # 添加可執行代碼 add_executable(HelloApp ${SRCS}) # 鏈接Qt內褲 target_link_libraries(HelloApp PRIVATE Qt6::Core Qt6::Gui)
這里老周學會了偷懶,用 file 指令找出 includes 目錄下所有擴展名為 .h 的檔案, 以及 src 目錄下所有擴展名為 .cpp 的檔案,然后把結果存到 SRCS 變數中,在 add_executable 命令執行時直接把 SRCS 傳給它,這樣做的好處是不用每新建一個檔案都要手動添加一次了,當 IDE 提示找不到頭檔案時,執行一次 CMake 配置就會觸發 file 命令,專案的目錄結構大致長這樣:

同理,這里咱們只用到 Core 和 Gui 兩個模塊,不需要 Widgets,
CustWindow 類派生自 QRasterWindow 類,重寫 paintEvent 方法,自行繪制視窗內容,
#include <QRasterWindow> #include <QPaintEvent> #ifndef __CUSTWINDOW_H__ #define __CUSTWINDOW_H__ class CustWindow : public QRasterWindow { Q_OBJECT protected: void paintEvent(QPaintEvent* event) override; }; #endif
下面是實作代碼,
#include "../includes/CustWindow.h" #include <QPainter> void CustWindow::paintEvent(QPaintEvent *event) { QPainter painter; painter.begin(this); // 要繪制的區域 QRect rect = event->rect(); // 先刷刷墻壁 painter.fillRect(rect, QColor("blue")); // 刷累了畫個大餅充饑 // 換支筆 QPen pen(QColor("yellow"), 3.0f); painter.setPen(pen); rect.adjust(50, 50, -50, -50); painter.drawEllipse(rect); // 收工 painter.end(); }
在實體化 QPainter 時,可以把當前視窗指標 this 傳遞給 QPainter 的建構式;或者先呼叫無參建構式,然后呼叫 begin 方法傳遞 this,前面說過,QRasterWindow 類的父類中有 QPaintDevice,所以咱們的視窗類自然就能直接傳給 QPainter 物件了,
這里頭的繼承關系是這樣的:
QWindow、QPaintDevice => QPaintDeviceWindow => QRasterWindow => CustWindow
C++ 是可以多繼承的,所以 QPaintDevice 能有兩個基類,
app.cpp 檔案中寫 main 函式,
#include "../includes/CustWindow.h" #include <QGuiApplication> int main(int argc, char* argv[]) { QGuiApplication app(argc, argv); // 實體化視窗 CustWindow window; // 設定標題和大小 window.setTitle("Bug App"); window.resize(450, 450); // 顯示視窗 window.show(); return app.exec(); }
運行一下,看看咱們畫的大餅,又大又黃,

看到這里,相信大伙伴們都了解 QWindow 怎么玩了,于是,咱們回歸標題,這個類到底干嗎呢?與 QWidget 類比如何?
1、QWindow 比 QWidget 更復雜,更難用,更麻煩,是一盞很浪費油的燈;
2、可是,它也不是沒用的,QWidget 測重組件化,封裝得好,開柜即用,方便組裝,而 QWindow 更抽象,更高級,更靈活,用來裝逼直接爆表,比如你有一個視窗只用來畫一個圖表,告訴用戶,他最近抑郁癥發作的頻率和趨勢,以及預測什么時候無可救藥,這種情形就很適合使用 QWindow 來創建視窗,
總的來說,QWindow 類能做的事情更多,但需要投入的開發成本更高,代碼量更嚇人,我們知道,其實視窗上的控制元件(比如按鈕、標簽、復選框等)本質上也是視窗物件——只是嵌套在頂層視窗中,成了子視窗罷了,
QWindow 物件也可以嵌套使用的,這個老周會在下一篇水文中介紹,故,QWindow 類不僅能靈活的創建視窗,也能自制許多控制元件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550921.html
標籤:其他
上一篇:docker常用命令
下一篇:返回列表
