Qt C++ 教程:无边框窗体 + 自定义标题栏 + 圆角 + 拖拽拉升 + 阴影

在使用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 窗口的样式限制,也为开发专业级桌面应用提供了坚实的基础。

该方案适用于登录界面、主程序窗口、弹窗、设置面板等多种场景,具备良好的工程价值和扩展潜力。

往期推荐

手撕线程池:C++程序员的能力试金石

打破认知:Linux管道到底有多快?

C++的三种参数传递机制:从底层原理到实战

顺时针螺旋移动法 | 彻底弄懂复杂C/C++嵌套声明、const常量声明!!!

阿里面试官:千万级订单表新增字段,你会怎么弄?

C++内存模型实例解析

字节跳动2面:为了性能,你会牺牲数据库三范式吗?

字节C++一面:enum和enum class的区别?

Redis分布式锁:C++高并发开发的必修课

相关推荐
mahuifa2 小时前
C++(Qt)软件调试---binutils工具集详解(39)
linux·c++·软件调试·binutils
feiyangqingyun2 小时前
全网首发/Qt结合ffmpeg实现rist推拉流/可信赖的互联网流媒体协议/跨平台支持各个系统
qt·ffmpeg·rist推拉流
泽虞3 小时前
《Qt应用开发》笔记p5
linux·开发语言·c++·笔记·qt·算法
qq_433554543 小时前
C++ 完全背包时间优化、完全背包空间优化
开发语言·c++·动态规划
周之鸥3 小时前
Qt 项目国际化从零到一:用 Qt Linguist 实现多语言动态切换(含源码与踩坑指南)
qt·i18n·cmake·qmake·linguist·lupdate·lrelease
打码的猿3 小时前
在Qt中实现SwitchButton(开关按钮)
开发语言·qt·ui
友友马3 小时前
『 QT 』QT窗口坐标体系详解
开发语言·qt
cxr8283 小时前
AI智能体赋能金融研究领域之仿真:流动性风暴下的高维战略 —— QT驱动的系统性失位与方舟部署蓝图
人工智能·qt·金融·ai赋能
另寻沧海10 小时前
测试中的 AAA 模式与 Given–When–Then 模式详解
c++·单元测试·测试覆盖率