【Qt6】QWindow類可以做什麼

来源:https://www.cnblogs.com/tcjiaan/archive/2023/04/23/17346259.html
-Advertisement-
Play Games

原來的水文標題是“用 VS Code 搞 Qt6”,想想還是直接改為“Qt6”,反正這個用不用 VS Code 也能搞。雖然我知道大伙伴們都很討厭 CMake,但畢竟這廝幾乎成了 C++ 的玩家規範了。Qt 也算識大體,支持用 CMake 來構建程式。所以,只要你用的是能寫 C++ 的工具,理論上都 ...


原來的水文標題是“用 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 類不僅能靈活的創建視窗,也能自製許多控制項。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 交互設計原則有很多,《小紅書的52條設計原則》可以學習下,非常棒的輸出,值得做產品設計的童鞋學習一下。 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹 VLD 源碼的編譯。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. VLD 庫的依賴文件 以 vld2.5.1 版本為例,下載源碼 後,源碼包中各文件的用途可看本人另一篇博客 【VLD】源碼文件概覽。使用 vld2.5.1- ...
  • 本案例實現一個test命名空間,此命名空間內有兩個函數,分別為getName()和getNameSpace(); 聲明命名空間及函數 namespace test{ const std::string& getName()和(); const std::string& getNameSpace(); ...
  • 常量指針與指針常量 #include<iostream> using namespace std; int main() { int a = 10; int b = 20; // 常量指針與指針常量 // 1.常量指針 const修飾指針 指針的指向是可以修改的(指針變數中存的地址值可以修改) 指針 ...
  • 學習Spring源碼的建議 閱讀Spring官方文檔,瞭解Spring框架的基本概念和使用方法。 下載Spring源碼,可以從官網或者GitHub上獲取。 閱讀Spring源碼的入口類,瞭解Spring框架的啟動過程和核心組件的載入順序。 閱讀Spring源碼中的註釋和文檔,瞭解每個類和方法的作用和 ...
  • 平面上有n個點(n<=100),每個點的坐標均在-10000~10000之間,其中的一些點之間有連線。 若有連線,則表示可從一個點到達另一個點,即兩點間有通路,同路的距離為兩點間的直線距離。現在的任務是找出從一點到另一點之間的最短路徑。 小提示: 兩點的距離:如果點$A$坐標為$(x_A,y_A)$ ...
  • 本文首發於公眾號:Hunter後端 原文鏈接:Django筆記二十九之中間件介紹 這一節介紹一下 Django 的中間件。 關於中間件,官方文檔的解釋為:中間件是一個嵌入 Django 系統的 request 和 response 的鉤子框架,是一個能夠全局改變 Django 輸入/輸出的系統。 我 ...
  • 面向對象特性 封裝 在程式設計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程式外部看不到,其含義是其他程式無法調用。要瞭解封裝,離不開“私有化”,就是將類或者是函數中的某些屬性限制在某個區域之內,外部無法調用。 封裝的作用: 1、保護隱私(把不想別人知道的東 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...