在使用Qt做界面開發時,為了提升視覺效果,經常會採用無邊框窗口設計。

實現無邊框其實很簡單,一行代碼搞定。

setWindowFlag(Qt::FramelessWindowHint);

由於移除了系統默認標題欄,窗口失去了原生的移動和縮放功能,需通過代碼手動實現。

本文旨在使用 Qt 框架實現一個無邊框窗口,具備以下核心功能:

  • 去除系統默認的窗口邊框和標題欄;
  • 支持通過鼠標拖動實現窗口整體移動;
  • 預留最小化、最大化、關閉等標準窗口操作的接口結構(按鈕功能將在後續章節中實現)。

該方案適用於希望自定義窗口外觀、提升界面美觀度的 Qt 開發者,尤其適合初學者理解無邊框窗口的基本實現原理。

核心技術點

技術

説明

setWindowFlags(Qt::FramelessWindowHint)

移除系統默認邊框和標題欄

setWindowFlags(Qt::WindowSystemMenuHint)

保留系統右鍵菜單(如移動、大小調整等),提升用户體驗

重寫 mousePressEvent

記錄鼠標按下時的相對位置,為拖動做準備

重寫 mouseMoveEvent

根據鼠標移動實時更新窗口位置,實現拖動效果

Part1無邊框窗口實現

1.1、頭文件定義

BasicFramelessWindow.h

#pragma once
#include <QtWidgets/QWidget>
#include <QMouseEvent>
class BasicFramelessWindow : public QWidget
{
    Q_OBJECT
public:
    explicit BasicFramelessWindow(QWidget *parent = nullptr);
    ~BasicFramelessWindow();
protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
private:
    QPoint dragPosition; // 記錄鼠標按下時相對於窗口左上角的偏移
};

説明

  • 繼承自 QWidget,構建基礎窗口;
  • 定義兩個受保護的事件處理函數,用於捕獲鼠標行為;
  • 使用 dragPosition 存儲拖動起始點與窗口座標之間的偏移量。

1.2、源文件實現

BasicFramelessWindow.cpp

#include "BasicFramelessWindow.h"
BasicFramelessWindow::BasicFramelessWindow(QWidget *parent)
    : QWidget(parent)
{
    // 設置窗口為無邊框,並保留系統菜單(右鍵可調出系統操作)
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
    // 關閉透明背景(確保背景正常顯示)
    setAttribute(Qt::WA_TranslucentBackground, false);
    // 設置固定窗口大小
    setFixedSize(600, 400);
    // 設置樣式:白色背景 + 灰色邊框(便於觀察)
    setStyleSheet("background-color: white; border: 1px solid gray;");
}
BasicFramelessWindow::~BasicFramelessWindow()
{
    // 析構函數(當前無需特殊處理)
}
// 鼠標按下事件:記錄拖動起始位置
void BasicFramelessWindow::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        // 計算鼠標點擊位置與窗口左上角的偏移
        dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
        event->accept(); // 接受事件,防止被其他控件處理
    }
}
// 鼠標移動事件:執行窗口拖動
void BasicFramelessWindow::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        // 根據偏移量移動窗口
        move(event->globalPosition().toPoint() - dragPosition);
        event->accept();
    }
}

關鍵邏輯解析

  • event->globalPosition().toPoint():獲取鼠標在屏幕座標系中的位置;
  • frameGeometry().topLeft():獲取窗口在屏幕上的左上角座標;
  • 兩者的差值即為“拖動錨點”,確保鼠標始終“抓着”窗口同一位置移動;
  • move(...)直接改變窗口位置,實現平滑拖動。

運行程序後,將顯示一個 600×400 的白色無邊框窗口

雖然界面空白,但已具備以下能力:

  • 可通過鼠標左鍵點擊並拖動窗口任意位置實現移動
  • 窗口無系統標題欄和邊框
  • 保留系統右鍵菜單(可通過右鍵點擊任務欄圖標調出“移動”“大小”等選項)

Part2實現自定義標題欄

實現自定義標題欄:支持拖動、雙擊最大化與動態按鈕切換

2.1、功能目標

功能

説明

自定義標題欄

替代系統默認標題欄,支持自由佈局與樣式定製

三按鈕控制

最小化、最大化/還原、關閉,通過信號與主窗口通信

動態圖標切換

窗口最大化時,“最大化按鈕”自動變為“還原圖標”

拖動移動

鼠標按住標題欄可拖動窗口(非最大化狀態下)

雙擊切換狀態

雙擊標題欄實現最大化 ↔ 正常狀態切換

2.2、標題欄組件實現(TitleBar)

我們將標題欄封裝為獨立組件 TitleBar,繼承自 QWidget,便於在不同窗口中複用。

1. 頭文件:TitleBar.h

#pragma once
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include <QMouseEvent>
class TitleBar : public QWidget
{
    Q_OBJECT
public:
    explicit TitleBar(QWidget* parent = nullptr);
    // 設置當前是否為最大化狀態,用於圖標切換
    void setMaximized(bool maximized);
signals:
    void signalMinimize();           // 發送最小化信號
    void signalMaximizeRestore();    // 發送最大化/還原切換信號
    void signalClose();              // 發送關閉信號
protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseDoubleClickEvent(QMouseEvent* event) override;
private:
    QPushButton* btnMin;           // 最小化按鈕
    QPushButton* btnMaxRestore;    // 最大化/還原按鈕
    QPushButton* btnClose;         // 關閉按鈕
    QLabel* titleLabel;            // 標題標籤
    QPoint dragPosition;           // 拖動偏移量
    bool isMaximized;              // 當前是否最大化
};

設計説明

  • 這裏頭定義了個 TitleBar 類,繼承自 QWidget。
  • public 裏有構造函數和設置圖標狀態的 setMaximized 方法;
  • signals 那塊是三個信號,對應最小化、最大化 / 還原、關閉這幾個動作;
  • protected 裏重載了鼠標按下、移動和雙擊事件;
  • private 裏就是那三個按鈕、標題標籤、記錄拖動位置的 dragPosition,還有標記是否最大化的 isMaximized。

2. 源文件:TitleBar.cpp

#include "TitleBar.h"
#include <QMouseEvent>
#include <QStyle>
#include <QApplication>
TitleBar::TitleBar(QWidget* parent)
    : QWidget(parent)
{
    isMaximized = false;
    setFixedHeight(35);  // 標題欄高度
    setAttribute(Qt::WA_StyledBackground, true); // 啓用樣式表渲染
    // 設置整體樣式
    setStyleSheet(R"(
        TitleBar {
            background-color: rgb(223, 235, 250);
        }
        QPushButton {
            border: none;
            background-color: transparent;
            min-width: 45px;
            min-height: 35px;
        }
        QPushButton:hover {
            background-color: rgb(211, 226, 237);
        }
        QPushButton:pressed {
            background-color: rgba(255, 255, 255, 50);
        }
    )");
    // 標題文本
    titleLabel = new QLabel("My App");
    titleLabel->setStyleSheet("border:none; background:transparent; font-weight:bold; padding-left:10px;");
    // 創建按鈕並設置圖標(需確保資源已添加到qrc)
    btnMin = new QPushButton();
    btnMin->setIcon(QIcon(":/new/prefix1/resources/min.png"));
    btnMaxRestore = new QPushButton();
    btnMaxRestore->setIcon(QIcon(":/new/prefix1/resources/max.png"));
    btnClose = new QPushButton();
    btnClose->setIcon(QIcon(":/new/prefix1/resources/close.png"));
    // 佈局管理
    auto mainLayout = new QHBoxLayout(this);
    mainLayout->addWidget(titleLabel);
    mainLayout->addStretch();
    mainLayout->addWidget(btnMin);
    mainLayout->addWidget(btnMaxRestore);
    mainLayout->addWidget(btnClose);
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);
    // 信號連接
    connect(btnMin, &QPushButton::clicked, this, &TitleBar::signalMinimize);
    connect(btnMaxRestore, &QPushButton::clicked, this, &TitleBar::signalMaximizeRestore);
    connect(btnClose, &QPushButton::clicked, this, &TitleBar::signalClose);
}

圖標狀態切換

void TitleBar::setMaximized(bool maximized)
{
    isMaximized = maximized;
    btnMaxRestore->setIcon(
        isMaximized ?
            QIcon(":/new/prefix1/resources/restore.png") :  // 還原圖標
            QIcon(":/new/prefix1/resources/max.png")       // 最大化圖標
    );
}

鼠標事件處理

void TitleBar::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton) {
        dragPosition = event->globalPosition().toPoint() - parentWidget()->frameGeometry().topLeft();
    }
}
void TitleBar::mouseMoveEvent(QMouseEvent* event)
{
    if ((event->buttons() & Qt::LeftButton) && !isMaximized) {
        parentWidget()->move(event->globalPosition().toPoint() - dragPosition);
    }
}

⚠️ 注意:僅在非最大化狀態下允許拖動,避免誤操作。

void TitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
    Q_UNUSED(event);
    emit signalMaximizeRestore(); // 雙擊觸發最大化/還原
}

雙擊標題欄即可切換窗口狀態,符合用户習慣。

2.3、整合至主窗口(BasicFramelessWindow)

接下來將 TitleBar 嵌入主窗口,並連接信號槽實現完整控制邏輯。

1. 更新頭文件:BasicFramelessWindow.h

#pragma once
#include <QWidget>
#include <QMouseEvent>
#include "TitleBar.h"
class BasicFramelessWindow : public QWidget
{
    Q_OBJECT
public:
    explicit BasicFramelessWindow(QWidget* parent = nullptr);
    ~BasicFramelessWindow();
protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
private slots:
    void onMinimize();
    void onMaxRestore();
    void onClose();
private:
    TitleBar* titleBar;
    bool isMaximized;
    QPoint dragPosition;
};

2. 實現主窗口邏輯:BasicFramelessWindow.cpp

#include "BasicFramelessWindow.h"
#include <QVBoxLayout>
BasicFramelessWindow::BasicFramelessWindow(QWidget* parent)
    : QWidget(parent), isMaximized(false)
{
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
    setAttribute(Qt::WA_TranslucentBackground, false);
    setFixedSize(600, 400);
    setStyleSheet("background-color: white; border: 1px solid gray;");
    // 創建標題欄
    titleBar = new TitleBar(this);
    // 連接信號與槽
    connect(titleBar, &TitleBar::signalMinimize,        this, &BasicFramelessWindow::onMinimize);
    connect(titleBar, &TitleBar::signalMaximizeRestore, this, &BasicFramelessWindow::onMaxRestore);
    connect(titleBar, &TitleBar::signalClose,           this, &BasicFramelessWindow::onClose);
    // 主佈局
    auto mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(titleBar);
    mainLayout->addStretch();
    mainLayout->setSpacing(0);
    mainLayout->setContentsMargins(1, 1, 1, 1); // 留出邊框間隙
}
BasicFramelessWindow::~BasicFramelessWindow() = default;
void BasicFramelessWindow::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton) {
        dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
        event->accept();
    }
}
void BasicFramelessWindow::mouseMoveEvent(QMouseEvent* event)
{
    if (event->buttons() & Qt::LeftButton) {
        move(event->globalPosition().toPoint() - dragPosition);
        event->accept();
    }
}
// 槽函數實現
void BasicFramelessWindow::onMinimize()
{
    showMinimized();
}
void BasicFramelessWindow::onMaxRestore()
{
    if (isMaximized) {
        showNormal();
    } else {
        showMaximized();
    }
    isMaximized = !isMaximized;
    titleBar->setMaximized(isMaximized); // 同步按鈕圖標
}
void BasicFramelessWindow::onClose()
{
    close();
}

信號-槽機制實現鬆耦合; 

窗口狀態變化後同步更新標題欄圖標; 

支持最小化、最大化/還原、關閉全功能。

2.4、運行效果

Part3實現無邊框窗口

接下來我們將實現一個關鍵功能: 像系統窗口一樣,通過鼠標拖動窗口邊緣或角落來調整大小。

這包括:

  • 上、下、左、右四邊拉伸
  • 四個角(左上、右上、左下、右下)斜向縮放
  • 鼠標懸停時顯示對應方向的光標(↔、↕、↘ 等)
  • 動態調整窗口尺寸,支持最小寬高限制

3.1、功能説明

功能

説明

捕捉鼠標位置

判斷鼠標是否位於窗口邊緣或角落區域

改變鼠標形狀

顯示為 ↔(水平)、↕(垂直)、↘(對角線)等系統縮放光標

響應鼠標拖動

按下後拖動鼠標,動態調整窗口大小

限制最小尺寸

防止窗口被縮到不可見或過小

3.2、代碼實現

核心類設計:CustomWindow

我們將窗口縮放功能封裝在 CustomWindow 類中,繼承自 QWidget,作為所有需要無邊框縮放功能窗口的基類。

CustomWindow.h

#pragma once
#include <QtWidgets/QWidget>
#include<QMouseEvent>
#include<qpoint.h>


class CustomWindow :public QWidget
{
    Q_OBJECT
public:
    explicit CustomWindow(QWidget* parent = nullptr);
protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event)override;
    void mouseMoveEvent(QMouseEvent* event)override;
    void leaveEvent(QEvent* event)override;
private:
    enum ResizeRegion {
        NoEdge = 0,
        Left,
        Right,
        Top,
        Bottom,
        TopLeft,
        TopRight,
        BottomLeft,
        BottomRight
    };
    const int EDGE_MARGIN = 8; //邊緣檢測範圍


        ResizeRegion getResizeRegion(const QPoint& pos);
    bool isResizing = false; //是否正在縮放


        ResizeRegion currentRegion = NoEdge;
    QPoint dragStartGlobalPos; //鼠標拖動起點
    QRect originalGeometry;  //拖動時窗口原始位置
};

代碼詳解:

  • 這裏定義了個 CustomWindow 類,繼承自 QWidget。
  • protected 裏重載了鼠標按下、釋放、移動和離開事件。
  • private 裏搞了個枚舉 ResizeRegion,把窗口的邊緣和角落都分了類,從 NoEdge(不在邊緣)到各個方向的邊緣和角落。
  • EDGE_MARGIN,設成 8,就是邊緣檢測的範圍,鼠標離邊緣這麼近就算是在邊緣區域了。
  • getResizeRegion 方法是用來判斷鼠標位置屬於哪個區域的。
  • 變量isResizing 標記是不是正在縮放,currentRegion 記當前鼠標在哪個區域,dragStartGlobalPos 是鼠標開始拖動時的全局位置,originalGeometry 是拖動前窗口的位置和大小。

CustomWindow.cpp

#include "CustomWindow.h"
CustomWindow::CustomWindow(QWidget* parent):QWidget(parent)
{
        setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); //無邊框
        setMouseTracking(true); //鼠標移動觸發mouseMoveEvent
}

構造函數裏,先設了無邊框,然後 setMouseTracking (true),這樣鼠標在窗口上動的時候,不用按鼠標鍵也能觸發 mouseMoveEvent,方便檢測鼠標位置換光標。

//準備拖動
void CustomWindow::mousePressEvent(QMouseEvent* event)
{
        if (event->button() == Qt::LeftButton && currentRegion != NoEdge) {
                isResizing = true;
                dragStartGlobalPos = event->globalPosition().toPoint();
                originalGeometry = geometry();
        }
        QWidget::mousePressEvent(event);
}

鼠標按下事件裏,要是按的是左鍵,而且當前鼠標在邊緣區域(不是 NoEdge),就把 isResizing 設為 true,記下拖動開始時鼠標的全局位置 dragStartGlobalPos,還有當時窗口的位置大小 originalGeometry。

//結束拖動
void CustomWindow::mouseReleaseEvent(QMouseEvent* event)
{
        isResizing = false;
        QWidget::mouseReleaseEvent(event);
}

鼠標釋放的時候,就把 isResizing 改成 false,結束縮放。

//設置光標或拖動縮放
void CustomWindow::mouseMoveEvent(QMouseEvent* event)
{
        if (isResizing) {
                QPoint delta = event->globalPosition().toPoint() - dragStartGlobalPos;
                QRect newGeom = originalGeometry;
                switch (currentRegion) {
                case Left:
                        newGeom.setLeft(originalGeometry.left() + delta.x());
                        break;
                case Right:
                        newGeom.setRight(originalGeometry.right() + delta.x());
                        break;
                case Top:
                        newGeom.setTop(originalGeometry.top() + delta.y());
                        break;
                case Bottom:
                        newGeom.setBottom(originalGeometry.bottom() + delta.y());
                        break;
                case TopLeft:
                        newGeom.setTopLeft(originalGeometry.topLeft() + delta);
                        break;
                case TopRight:
                        newGeom.setTopRight(originalGeometry.topRight() + delta);
                        break;
                case BottomLeft:
                        newGeom.setBottomLeft(originalGeometry.bottomLeft() + delta);
                        break;
                case BottomRight:
                        newGeom.setBottomRight(originalGeometry.bottomRight() + delta);
                        break;
                default:
                        break;
                }
                if (newGeom.width() >= minimumWidth() && newGeom.height() >= minimumHeight()) {
                        setGeometry(newGeom);
                }
        }
        else {
                //設置鼠標光標形狀
                ResizeRegion region = getResizeRegion(event->pos());
                currentRegion = region;
                switch (region) {
                case Left:
                case Right:
                        setCursor(Qt::SizeHorCursor);
                        break;
                case Top:
                case Bottom:
                        setCursor(Qt::SizeVerCursor);
                        break;
                case TopLeft:
                case BottomRight:
                        setCursor(Qt::SizeFDiagCursor);
                        break;
                case TopRight:
                case BottomLeft:
                        setCursor(Qt::SizeBDiagCursor);
                        break;
                default:
                        unsetCursor();
                        break;
                }
        }
        QWidget::mouseMoveEvent(event);
}

鼠標移動事件分兩種情況:要是正在縮放(isResizing 為 true),就先算一下鼠標移動的距離 delta—— 當前鼠標全局位置減去開始拖動時的位置。然後根據 currentRegion,也就是當前縮放的區域,調整 newGeom(新的窗口位置大小)。比如是 Left 區域,就調整窗口的左邊界;是 Right 就調右邊界,四個角也各有對應的調整方式。調整完了,得檢查新的寬度和高度是不是不小於最小尺寸,符合條件就用 setGeometry 設置新的窗口形狀。

要是沒在縮放,就調用 getResizeRegion 判斷鼠標當前在哪個區域,然後根據區域換光標形狀。比如左右邊緣就用水平縮放的光標↔,上下邊緣用垂直縮放的光標↕,對角就用對應的對角線光標,不在邊緣就恢復默認光標。

//鼠標離開窗口,取消高亮
void CustomWindow::leaveEvent(QEvent* event)
{
        if (!isResizing)unsetCursor();
        QWidget::leaveEvent(event);
}

鼠標離開窗口時,要是沒在縮放,就把光標恢復默認。

//獲取邊緣區域
CustomWindow::ResizeRegion CustomWindow::getResizeRegion(const QPoint& pos)
{
        bool onLeft = pos.x() <= EDGE_MARGIN;
        bool onRight = pos.x() >= width() - EDGE_MARGIN;
        bool onTop = pos.y() <= EDGE_MARGIN;
        bool onButtom = pos.y() >= height() - EDGE_MARGIN;
        if (onTop && onLeft)return TopLeft;
        if (onTop && onRight)return TopRight;
        if (onButtom && onLeft)return BottomLeft;
        if (onButtom && onRight)return BottomRight;
        if (onTop)return Top;
        if (onButtom)return Bottom;
        if (onLeft)return Left;
        if (onRight)return Right;
        return NoEdge;
}

getResizeRegion 方法就是判斷鼠標位置 pos 屬於哪個區域。先看是不是在左、右、上、下邊緣(根據 EDGE_MARGIN 判斷),然後組合一下,比如又在頂部又在左邊,就是 TopLeft,依次類推,最後返回對應的區域。

3.3、整合代碼邏輯

因為 CustomWindow 是單獨的類,要在之前的 BasicFramelessWindow 裏用上它的縮放功能,得把邏輯整合一下。

先改 BasicFramelessWindow.h,讓它繼承 CustomWindow,把重複的鼠標事件處理邏輯去掉:

#pragma once
#include"TitleBar.h"
#include"CustomWindow.h"
class BasicFramelessWindow : public CustomWindow
{
    Q_OBJECT
public:
   explicit BasicFramelessWindow(QWidget *parent = nullptr);
    ~BasicFramelessWindow();
private slots:
    void onMinimize();
    void onMaxRestore();
    void onClose();
private:
    TitleBar* titleBar;
    bool isMaximized;
};

這樣就不用自己處理鼠標事件了,直接用 CustomWindow 的。

再改 BasicFramelessWindow.cpp,把原來的鼠標事件處理代碼刪掉,調整構造函數:

BasicFramelessWindow::BasicFramelessWindow(QWidget *parent)
    : CustomWindow(parent),isMaximized(false)

還要把原來的 setFixedSize () 改成 resize (),因為現在要能縮放窗口,不能固定大小了。

這麼一整合,窗口的鼠標拉伸功能就全實現啦,拽着邊緣或者角落就能隨便調整大小了。

Part4圓角與陰影效果

咱接下來搞 “無邊框窗口的圓角與陰影效果”。要是在現有程序裏改,得動的地方不少,所以咱直接建個新項目來實現這效果。

4.1、核心實現原理

技術點

實現方式

去除系統邊框

setWindowFlags(Qt::FramelessWindowHint)

支持透明背景

setAttribute(Qt::WA_TranslucentBackground)

實現圓角

在 contentWidget 上設置 border-radius 樣式

實現陰影

使用 QGraphicsDropShadowEffect 添加到內容控件

避免鋸齒

使用 QPainterPath 繪製抗鋸齒背景(可選增強)

4.2、代碼實現

CustomWindow.h

#pragma once
#include <QtWidgets/QWidget>
class CustomWindow : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWindow(QWidget* parent = nullptr);
    ~CustomWindow();
protected:
    void paintEvent(QPaintEvent* event) override;
private:
    void initUI();                    // 初始化UI
    QWidget* contentWidget;           // 主內容區域(圓角+陰影載體)
};

這裏定義了 CustomWindow 類,繼承自 QWidget。public 裏是構造和析構函數;protected 重載了 paintEvent 事件,後面繪圖要用;private 裏有個 contentWidget 指針當主內容區,還有個 initUI 方法用來初始化界面。

CustomWindow.cpp

#include "CustomWindow.h"
#include <QGraphicsDropShadowEffect>
#include <QPainter>
#include <QPainterPath>
#include <QVBoxLayout>
#include <QLabel>
CustomWindow::CustomWindow(QWidget* parent)
    : QWidget(parent)
{
    // 設置無邊框窗口
    setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
    // 啓用透明背景(關鍵!)
    setAttribute(Qt::WA_TranslucentBackground);
    // 初始大小
    resize(600, 400);
    // 初始化界面
    initUI();
}

構造函數裏,先設了無邊框窗口的標誌,然後 setAttribute (Qt::WA_TranslucentBackground) 是開啓透明背景,這樣後面的陰影和圓角才能正常顯示。

resize 把窗口初始大小設為 600x400,最後調用 initUI 初始化界面。

void CustomWindow::initUI() {
    contentWidget = new QWidget(this);
    contentWidget->setObjectName("contentWidget");
    contentWidget->setStyleSheet("#contentWidget {"
        "   background-color: white;"
        "   border-radius: 10px;"
        "   border: 1px solid #E0E0E0;"
        "}");

initUI 方法裏,先 new 了個 contentWidget。給它設了個對象名 “contentWidget”,後面寫樣式表好用。樣式表裏指定了 contentWidget 的背景是白色,邊框圓角 10 像素,還有個 1 像素的淺灰色邊框,這樣圓角效果就有了。

// 添加陰影
    QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
    shadow->setBlurRadius(20);
    shadow->setOffset(0, 0);
    shadow->setColor(QColor(0, 0, 0, 80));
    contentWidget->setGraphicsEffect(shadow);

這部分是加陰影。new 了個 QGraphicsDropShadowEffect 對象,setBlurRadius (20) 是説陰影的模糊半徑 20 像素,看着更柔和;setOffset (0,0) 是陰影偏移量,這兒設成不偏移;setColor 是陰影顏色,用了半透明的黑色。最後把這陰影效果設給 contentWidget。

auto label = new QLabel("窗口陰影與圓角", contentWidget);
    label->setAlignment(Qt::AlignCenter);
    label->setStyleSheet("font-size: 24px;");


    // 佈局
    QVBoxLayout* layout = new QVBoxLayout(contentWidget);
    layout->addWidget(label);


    QVBoxLayout* mainLayout = new QVBoxLayout(this);
    mainLayout->setContentsMargins(10, 10, 10, 10);  // 陰影邊距
    mainLayout->addWidget(contentWidget);
}

這裏建了個標籤 label,顯示 “窗口陰影與圓角”,設成居中對齊,字體大小 24 像素。然後用 QVBoxLayout 給 contentWidget 搞了佈局,把 label 加進去。外面又弄了個 mainLayout 當整個窗口的佈局,setContentsMargins 設了 10 像素的邊距,給陰影留地方,最後把 contentWidget 加進去。

void CustomWindow::paintEvent(QPaintEvent* event) {
    // 繪製透明背景
    QPainter painter(this);
    painter.fillRect(rect(), Qt::transparent);
}

paintEvent 裏用 QPainter 把窗口背景畫成透明的,避免出現不該有的底色,保證陰影能正常顯示。

CustomWindow::~CustomWindow()
{}

析構函數就空着,沒啥特殊要處理的。

運行效果

總結

通過以上實踐,我們構建了一個功能完備、結構清晰、視覺現代的 Qt 無邊框窗口框架。它不僅突破了傳統 Qt 窗口的樣式限制,也為開發專業級桌面應用提供了堅實的基礎。

該方案適用於登錄界面、主程序窗口、彈窗、設置面板等多種場景,具備良好的工程價值和擴展潛力。