在使用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 窗口的樣式限制,也為開發專業級桌面應用提供了堅實的基礎。
該方案適用於登錄界面、主程序窗口、彈窗、設置面板等多種場景,具備良好的工程價值和擴展潛力。