【Qt6】嵌套 QWindow

来源:https://www.cnblogs.com/tcjiaan/archive/2023/05/02/17368021.html
-Advertisement-
Play Games

在上個世紀的文章中,老周簡單介紹了 QWindow 類的基本使用——包括從 QWindow 類派生和從 QRasterWindow 類派生。 其實,QWindow 類並不是只能充當主視窗用,它也可以嵌套到父級視窗中,變成子級對象。咱們一般稱之為【控制項】。F 話不多講,下麵咱們用實際案例來說明。 這個 ...


在上個世紀的文章中,老周簡單介紹了 QWindow 類的基本使用——包括從 QWindow 類派生和從 QRasterWindow 類派生。

其實,QWindow 類並不是只能充當主視窗用,它也可以嵌套到父級視窗中,變成子級對象。咱們一般稱之為【控制項】。F 話不多講,下麵咱們用實際案例來說明。

這個例子中老周定義了兩個類:

MyControl:子視窗對象,充當控制項角色。這裡實現一個類似開關的控制項。【關閉】狀態下,控制項的背景呈現為灰色,金色方塊位於最左側;當控制項處於【開啟】狀態下,控制項背景為紅色,金色方塊位於最右側。 MyWindow:作為視窗使用,裡面包含 MyControl 對象。 先看 MyControl 類。
class MyControl : public QRasterWindow
{
    Q_OBJECT

public:
    MyControl(QWindow *parent = nullptr);

private:
    // “開啟”狀態時的背景色
    QColor _on_bgcolor;
    // “關閉”狀態時的背景色
    QColor _off_bgcolor;
    // 當前狀態
    bool _state;

signals:
    // 信號
    void stateChanged(bool isOn);

public:
    // 獲取狀態
    inline bool state() const { return _state; }
    // 修改狀態
    inline void setState(bool s)
    {
        _state = s;
        // 發出信號
        emit stateChanged(_state);
    }

protected:
    // 重寫方法
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent* evebt) override;
};

在私有成員中,兩個 QColor 類型的變數分別表示控制項處於【開】或【關】狀態時的背景色。_state 是一個布爾值,true就是【開】,false就是【關】,用來存儲控制項的當前狀態。

公共方法 state 獲取當前狀態,setState 方法用來修改當前狀態,同時會發出 stateChanged 信號。信號成員咱們先放一下,後文再敘。

兩個虛函數的重寫。paintEvent 負責畫出控制項的模樣;mousePressEvent 當滑鼠左鍵按下時改變控制項的狀態。實現點擊一下開啟,再點擊一下關閉的功能。

接下來是實現各個成員。先是構造函數。

MyControl::MyControl(QWindow* parent)
    :QRasterWindow::QRasterWindow(parent),
     _state(false),
     _on_bgcolor(QColor("red")),
     _off_bgcolor(QColor("gray"))
{

}

構造函數主要用來初始化幾個私有成員。下麵代碼實現滑鼠左鍵按下後更改狀態。

void MyControl::mousePressEvent(QMouseEvent *event)
{
    if(! (event->buttons() & Qt::MouseButton::LeftButton))
        return;  // 如果按的不是左鍵就 PASS
    
    this->setState(!this->state());
    update();
}

每次點擊後控制項的狀態都會取反(開變關,關變開),為了反映改變必須重新繪製控制項,所以要調用 update 方法。

paintEvent 中實現繪製的過程。

void MyControl::paintEvent(QPaintEvent* event)
{
    // 如果當前為不可見狀態,就不繪圖了
    if(!isExposed())
        return;
    // 要繪製的區域
    QRect rect = event -> rect();

    QPainter painter;
    painter.begin(this);
    // 根據狀態填充背景
    QBrush bgbrush;
    bgbrush.setStyle(Qt::SolidPattern);
    if(_state)
    {
        bgbrush.setColor(_on_bgcolor);
    }
    else
    {
        bgbrush.setColor(_off_bgcolor);
    }
    painter.fillRect(rect, bgbrush);

    QRect rectSq;
    // 如果是“開”的狀態,綠色矩形在右側
    if(_state)
    {
        rectSq.setX(rect.width() / 3 * 2);
        rectSq.setWidth(rect.width() / 3);
    }
    // 如果為“關”的狀態,綠色矩形在左側
    else{
        rectSq.setWidth(rect.width() / 3);
    }
    rectSq.setHeight(rect.height());
    painter.fillRect(rectSq, QColor("gold"));
    painter.end();
}

繪製分兩步走。第一步是填充背景矩形,如果【開】就填充紅色,如果【關】就填充灰色。此處用到了 QBrush 對象。根據不同顏色調用 setColor 方法來設置。這裡要註意 QBrush 對象要調用 setStyle 方法設置畫刷樣式為 SolidPattern。這是因為 QBrush 類預設的 style 是 NoBrush,因此要手動設置一下,不然看不到繪製。

第二步是繪製小方塊。當控制項狀態為【開】時方塊在右邊,狀態為【關】時方塊在左邊。小方塊的寬度是控制項寬度的 1/3,高度與控制項相同。要顯式調用 setHeight 方法設置矩形高度(因為它預設為0)。記得小方塊的左上角的 X 坐標也要調整的。

 

下麵是視窗 MyWindow 的成員。

class MyWindow : public QRasterWindow
{
    Q_OBJECT

public:
    MyWindow(QWindow *parent = nullptr);

private:
    MyControl *_control;
    void initUI();

protected:
    void paintEvent(QPaintEvent *event) override;
};

重寫的 paintEvent 方法負責畫視窗的背景。私有成員 initUI 用於初始化子視窗(MyControl對象)。initUI 方法在構造函數中調用。

MyWindow::MyWindow(QWindow *parent)
    :QRasterWindow::QRasterWindow(parent)
{
    initUI();
}

void MyWindow::initUI()
{
    // 初始化視窗
    setTitle("示常式序");
    resize(550, 450);
    setMinimumSize(QSize(340, 200));
    _control = new MyControl(this);
    // 初始化子視窗
    _control->setPosition(35, 40);
    _control->resize(120, 35);
    _control->setVisible(true);
}

setVisible 方法使用控制項變為可見,只有可見的對象才能被看到。

paintEvent 方法只負責畫視窗背景。

void MyWindow::paintEvent(QPaintEvent *event)
{
    // 只填充視窗
    QPainter painter(this);
    painter.fillRect(event->rect(), QColor("blue"));
    painter.end();
}

 

最後寫 main 函數。

int main(int argc, char** argv)
{
    QGuiApplication app(argc, argv);
    MyWindow wind;
    wind.show();
    return QGuiApplication::exec();
}

 

運行後,點一下子視窗,它就會改變顏色。

 

 關於控制項周圍有白邊(或黑邊)的問題:

這個問題比較不確定,不同平臺好像表現不一樣。Windows 下在控制項周圍會多出白色區域(也可能是黑色);在 Debian 下沒有出現白邊。所以,為了儘可能地避免跨平臺差異導致的問題,可以用 mask 讓控制項區域之外的地方變為透明,這樣白邊黑邊就會消失。

調用 setMask 方法要在重寫的 resizeEvent 方法中進行,這是為了響應控制項大小改變後,及時調整要透明的區域。

void MyControl::resizeEvent(QResizeEvent *event)
{
    setMask(QRect(QPoint(), event->size()));
}

 

剛纔,咱們 MyControl 類定義了 stateChanged 信號,併在修改控制項狀態後發出。接下來我們用一個 lambda 表達式來充當 slot,當控制項狀態改變後輸出調試信息(使用 qDebug 巨集)。

connect(_control, &MyControl::stateChanged, this, [](bool st){
    qDebug() << "當前狀態:" << st;
});

再次運行程式,每點擊一下控制項,控制台就會輸出一條調試信息。

 

 setMask 還有一個經典用途——製作透明視窗。下麵咱們順便討論一下如何做不規則形狀的視窗。

a、調用 setFlags 方法設置 WindowType::FramelessWindowHint 標誌,去掉視窗的邊框;

b、調用 setMask 方法時需要傳遞一個 QRegion 對象,它表示一個形狀區域。這個區域可以是矩形,也可以是圓形,當然也可以是多邊形。設置 mask 後,指定區域外的內容變成透明,並且不會響應用戶的輸入事件(滑鼠、鍵盤等)。QRegion 對象可以進行 and、or 等運算,多個區域可以進行交集或並集處理,形成各種不規則圖形。

 

咱位做個例子。

#ifndef APP_H
#define APP_H
#include <QColor>
#include <QSize>
#include <QWindow>
#include <QRasterWindow>
#include <QPainter>
#include <QRect>
#include <QPaintEvent>
#include <QRegion>
#include <QList>
#include <QPolygon>
#include <QPoint>
#include <QResizeEvent>

class CustWindow : public QRasterWindow
{
    Q_OBJECT

public:
    CustWindow(QWindow *parent = nullptr);

protected:
    void paintEvent(QPaintEvent* event) override;
    void resizeEvent(QResizeEvent* event) override;

    // 視窗沒了邊框無法用常規操作,於是實現雙擊關閉視窗
    void mouseDoubleClickEvent(QMouseEvent *event) override;
};
#endif
CustWindow::CustWindow(QWindow* parent)
    :QRasterWindow::QRasterWindow(parent)
{
    setFlags(Qt::WindowType::FramelessWindowHint|Qt::WindowType::BypassWindowManagerHint);
    // 視窗位置和大小
    setGeometry(600, 500, 400, 400);
}

void CustWindow::paintEvent(QPaintEvent* event)
{
    QPainter p(this);
    p.fillRect(event->rect(), QColor("deeppink"));
    p.end();
}

void CustWindow::resizeEvent(QResizeEvent *event)
{
    QRegion rg0(QRect(QPoint(), event->size()));

    QList<QPoint> pt1 = {
        {15,16},
        {180, 300},
        {320, 300},
        {150, 57}
    };
    QPolygon pl1(pt1);
    QRegion rg1(pl1);

    QList<QPoint> pt2 = {
        {315, 20},
        {25, 160},
        {160, 320},
        {240, 150},
        {330, 30}
    };
    QPolygon pl2(pt2);
    QRegion rg2(pl2);

    QRegion rg3 = rg0 & (rg1 | rg2);
    setMask(rg3);
}

void CustWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        this->close();   // 關閉視窗
    }
}

調用setFlags方法時,用到了 WindowType::BypassWindowManagerHint 值。Windows 下沒有影響,主要是針對 X11,去除第三方主題產生的視窗陰影。

在 resize 事件處理代碼中,QPolygon 類用來構建多邊形,通過 QList<QPoint> 對象來指定頂點列表。上面代碼中是兩個不規則圖形並集後,再與視窗的矩形區域取交集。最後把這個區域外的內容都設置成了透明。

運行效果如下。

 


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

-Advertisement-
Play Games
更多相關文章
  • 先聲明一下前期的一些手欠欠兒的操作導致oracl登錄不進去了,起先是清理磁碟空間的時候誤刪除了orcle DBF數據文件後無法進入系統,plsql登錄報錯如下: 一般情況下,刪除表空間的正確方法是:DROP TABLESPACE BDCDJ INCLUDING CONTENTS AND DATAFI ...
  • (DML語言) 前言 前面的兩篇文章中,我們已經對MySQL有了基本瞭解。 並且知道了怎麼用工具連接資料庫?怎麼創建資料庫?怎麼創建表? 這一篇呢我們就來看看怎麼在我們創建的表中插入數據、刪除數據和修改數據。也就是上一篇文章中提到的DML 數據操作語言 準備 根據上一章所說的,我們創建一個db_xi ...
  • 功能04-達人探店 5.功能04-達人探店 5.1發佈&查看探店筆記 5.1.1發佈探店筆記 探店筆記類似點評網站的評價,往往是圖文結合。對應的表有兩個: tb_blog:探店筆記表,包含筆記中的標題、文字、圖片等 tb_blog_comments:其他用戶對探店筆記的評價 /*表: tb_blog ...
  • (使用MySQL) 前言 根據上一篇文章【必知必會的MySQL知識】①初探MySQL的內容,想必您對MySQL資料庫有了一個整體的瞭解了,並且應該在自己電腦上已經安裝上了MySQL。 這一篇呢我們來說一說這麼連接上資料庫並且使用它。 啟動MySQL服務 前面MySQL安裝的文章手把手教你安裝MySQ ...
  • html基礎標簽 學習網站:https://www.acwing.com/ 學習查詢網站:https://developer.mozilla.org/zh-CN/ !+tab自動出現框架 1.1 文檔結構 html的所有標簽都為樹形結構,例如: <!DOCTYPE html> <html lang= ...
  • 圖片 png 無損壓縮,尺寸體積要比jpg的大,適合做小圖標 jpg 採用壓縮演算法,有一點失真,比png體積要小,適合做中大圖片 gif 一般是做動圖的 webp 同時支持有損或者無損壓縮,相同質量的圖片,webp具有更小的體積 css的盒子模型 標準盒子模型 margin/border/paddi ...
  • 最近翻開源代碼的時候看到了一種很有意思的switch用法,分享一下。 註意這裡討論的不是typed switch,也就是case語句後面是類型的那種。 直接看代碼: func (s *systemd) Status() (Status, error) { exitCode, out, err := ...
  • Java讀取資料庫表 package com.easycrud.builder; import com.easycrud.utils.PropertiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ja ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...