C++ Qt QMainWindow实现无边框窗口自定义标题栏可拖拽移动拉伸改变窗口大小

news2024/12/23 13:38:37

本篇博客介绍C++ Qt QMainWindow实现无边框窗口,适用于win10/win11系统。

QMainWindow相对于QWidget多了dockedwidget功能,跟多人可能更喜欢用QMainWindow做主窗口,如果不需要dockedwidget功能,QMainWindow与QWidget做主窗口基本无差别。

效果图如下:
在这里插入图片描述
自带窗口阴影、圆角、可拉伸,拖拽。

具体实现过程如下:

一、编写无边框窗口基类CFramelessWindowBase

CFramelessWindowBase.h

/*

QMainWindow无边框窗口基类

可拉伸

其它QMainWindow窗口派生于该类即可

*/

#pragma once
#include <QMainWindow>

class CFramelessWindowBase : public QMainWindow
{
public:
	CFramelessWindowBase(QWidget* parent = nullptr);
	~CFramelessWindowBase();

protected:
	bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;

private:
	int mouse_margin = 5;
};

CFramelessWindowBase.cpp

#include "CFramelessWindowBase.h"
#include <qt_windows.h>
#include <windowsx.h>
#include <QWindow>
#include <windows.h>
#include <dwmapi.h>

#pragma comment(lib, "dwmapi.lib")


CFramelessWindowBase::CFramelessWindowBase(QWidget* parent)
	: QMainWindow(parent)
{
	setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
	setAttribute(Qt::WA_Hover);

	// 添加窗口阴影,窗口圆角
	HWND hWnd = reinterpret_cast<HWND>(winId());
	DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
	::DwmSetWindowAttribute(hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp));
	MARGINS shadow = { 1, 1, 1, 1 };
	DwmExtendFrameIntoClientArea((HWND)winId(), &shadow);
}

CFramelessWindowBase::~CFramelessWindowBase()
{
}

bool CFramelessWindowBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
	MSG* msg = static_cast<MSG*>(message);

	switch (msg->message)
	{
	case WM_NCHITTEST:
	{
		QPoint globalPos = QCursor::pos();
		int x = globalPos.x();
		int y = globalPos.y();

		//int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();   // bug : windows 在高分屏下,坐标值不正确
		//int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();

		int nX = x - this->geometry().x();
		int nY = y - this->geometry().y();

		// 如果鼠标位于内部子控件上,则不进行处理
		if (nX > mouse_margin && nX < width() - mouse_margin &&
			nY > mouse_margin && nY < this->height() - mouse_margin)
		{
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

		// 鼠标区域位于窗体边框,进行缩放
		if ((nX > 0) && (nX < mouse_margin))
			*result = HTLEFT;

		if ((nX > this->width() - mouse_margin) && (nX < this->width()))
			*result = HTRIGHT;

		if ((nY > 0) && (nY < mouse_margin))
			*result = HTTOP;

		if ((nY > this->height() - mouse_margin) && (nY < this->height()))
			*result = HTBOTTOM;

		if ((nX > 0) && (nX < mouse_margin) && (nY > 0)
			&& (nY < mouse_margin))
			*result = HTTOPLEFT;

		if ((nX > this->width() - mouse_margin) && (nX < this->width())
			&& (nY > 0) && (nY < mouse_margin))
			*result = HTTOPRIGHT;

		if ((nX > 0) && (nX < mouse_margin)
			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
			*result = HTBOTTOMLEFT;

		if ((nX > this->width() - mouse_margin) && (nX < this->width())
			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
			*result = HTBOTTOMRIGHT;

		return true;
	}
	}

	return QWidget::nativeEvent(eventType, message, result);
}

代码解释:
(1)在CFramelessWindowBase类设置窗口标志,去掉窗口边框,设置最大最小显示效果。

setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);

(2)增加windows窗口阴影与圆角:

// 添加窗口阴影,窗口圆角
HWND hWnd = reinterpret_cast<HWND>(winId());
DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
::DwmSetWindowAttribute(hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp));
MARGINS shadow = { 1, 1, 1, 1 };
DwmExtendFrameIntoClientArea((HWND)winId(), &shadow);

这里使用的是DWM API实现窗口阴影和圆角,圆角是windows窗口的圆角,不需要手动设置圆角大小。
(3)重写nativeEvent实现无边框窗口

bool CFramelessWindowBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
	MSG* msg = static_cast<MSG*>(message);

	switch (msg->message)
	{
	case WM_NCHITTEST:
	{
		QPoint globalPos = QCursor::pos();
		int x = globalPos.x();
		int y = globalPos.y();

		//int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();   // bug : windows 在高分屏下,坐标值不正确
		//int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();

		int nX = x - this->geometry().x();
		int nY = y - this->geometry().y();

		// 如果鼠标位于内部子控件上,则不进行处理
		if (nX > mouse_margin && nX < width() - mouse_margin &&
			nY > mouse_margin && nY < this->height() - mouse_margin)
		{
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

		// 鼠标区域位于窗体边框,进行缩放
		if ((nX > 0) && (nX < mouse_margin))
			*result = HTLEFT;

		if ((nX > this->width() - mouse_margin) && (nX < this->width()))
			*result = HTRIGHT;

		if ((nY > 0) && (nY < mouse_margin))
			*result = HTTOP;

		if ((nY > this->height() - mouse_margin) && (nY < this->height()))
			*result = HTBOTTOM;

		if ((nX > 0) && (nX < mouse_margin) && (nY > 0)
			&& (nY < mouse_margin))
			*result = HTTOPLEFT;

		if ((nX > this->width() - mouse_margin) && (nX < this->width())
			&& (nY > 0) && (nY < mouse_margin))
			*result = HTTOPRIGHT;

		if ((nX > 0) && (nX < mouse_margin)
			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
			*result = HTBOTTOMLEFT;

		if ((nX > this->width() - mouse_margin) && (nX < this->width())
			&& (nY > this->height() - mouse_margin) && (nY < this->height()))
			*result = HTBOTTOMRIGHT;

		return true;
	}
	}

	return QWidget::nativeEvent(eventType, message, result);
}

二、实现主窗口
派生于上面的CFramelessWindowBase,代码如下:
FramelessWindow.h

#pragma once

#include <QtWidgets/QMainWindow>
#include "CFramelessWindowBase.h"
#include "TitleBar.h"
#include "ContentWidget.h"
#include "LeftBar.h"
#include "CustomStatusBar.h"

class FramelessWindow : public CFramelessWindowBase
{
    Q_OBJECT

public:
    FramelessWindow(QWidget *parent = nullptr);
    ~FramelessWindow();

private slots:
    void OnClose();

private:
    TitleBar* m_pTitleBar = nullptr;
    ContentWidget* m_pContentWidget = nullptr;
    LeftBar* m_pLeftBar = nullptr;
    CustomStatusBar* m_pStatusBar = nullptr;
};

FramelessWindow.cpp

/*

主窗口

*/

#include "FramelessWindow.h"
#include <QVBoxLayout>
#include <QMessageBox>


FramelessWindow::FramelessWindow(QWidget *parent)
    : CFramelessWindowBase(parent)
{
    this->resize(800, 600);

    QWidget* pWidget = new QWidget(this);
    this->setCentralWidget(pWidget);

    m_pTitleBar = new TitleBar(pWidget);
    m_pTitleBar->SetTitleText(tr("QMainWindow Custom Title"));

    QString logo_qss = R"(
		QLabel{
			background-image:url(:/TitleBar/Resources/TitleBar/logo32.svg);
			background-position:center; 
			background-repeat: no-repeat;
			border:none
		}
	)";

    m_pTitleBar->SetTitleIcon(logo_qss);

    m_pContentWidget = new ContentWidget(pWidget);

    m_pLeftBar = new LeftBar(pWidget);
    m_pStatusBar = new CustomStatusBar(pWidget);

    QVBoxLayout* pVLay = new QVBoxLayout(pWidget);
    pVLay->setSpacing(0);
    pVLay->setContentsMargins(0, 0, 0, 0);

    pVLay->addWidget(m_pTitleBar);

    QHBoxLayout* pHLay = new QHBoxLayout(pWidget);
    pHLay->setSpacing(0);
    pHLay->addWidget(m_pLeftBar);
    pHLay->addWidget(m_pContentWidget);

    pVLay->addLayout(pHLay);
    pVLay->addWidget(m_pStatusBar);

    pWidget->setLayout(pVLay);

    connect(m_pTitleBar, &TitleBar::sig_Close, this, &FramelessWindow::OnClose);
}

FramelessWindow::~FramelessWindow()
{
}

void FramelessWindow::OnClose()
{
    QMessageBox::StandardButton resBtn = QMessageBox::question(this, tr("Tips"),
        tr("Are you sure you want to close the window?"),
        QMessageBox::Cancel | QMessageBox::Yes,
        QMessageBox::Yes);

    if (resBtn == QMessageBox::Yes) 
    {
        close();
    }
}

本篇博客源码下载:
https://download.csdn.net/download/yao_hou/89211306?spm=1001.2014.3001.5501

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1621776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring - 4 ( 11000 字 Spring 入门级教程 )

一&#xff1a;Spring IoC&DI 在前⾯的章节中, 我们学习了 Spring Boot 和 Spring MVC 的开发, 可以完成⼀些基本功能的开发了, 但是什么是 Spring 呢? Spring, Spring Boot 和 SpringMVC 又有什么关系呢? 咱们还是带着问题去学习.我们先看什么是Spring 1.1 Spring 是什…

万兆以太网MAC设计(7)ARP协议报文格式详解以及ARP层模块设计

文章目录 前言&#xff1a;1、ARP协议详解2、ARP工作机制 二、ARP_RX模块设计三、ARP_TX模块设计四、ARP_table模块5、仿真5.1、发送端5.2、接收端5.3、缓存表 总结 前言&#xff1a; 1、ARP协议详解 ARP数据格式&#xff1a; 硬件类型:表示硬件地址的类型。它的值为1表示以太…

微信小程序使用echarts组件实现饼状统计图功能

微信小程序使用echarts组件实现饼状统计图功能 使用echarts实现在微信小程序中统计图的功能&#xff0c;具体的实现步骤思路可进我主页查看我的另一篇博文https://blog.csdn.net/weixin_45465881/article/details/138171153进行查看&#xff0c;本篇文章主要使用echarts组件实…

javaEE初阶——多线程(九)——JUC常见的类以及线程安全的集合类

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 此篇文章与大家分享多线程专题的最后一篇文章:关于JUC常见的类以及线程安全的集合类 如果有不足的或者错误的请您指出! 目录 3.JUC(java.util.concurrent)常见的类3.1Callable接口3.2 RentrantLoc…

Yolov5 v7.0目标检测——详细记录环境配置、自定义数据处理、模型训练与常用错误解决方法(数据集为河道漂浮物)

1. Yolov5 YOLOv5是是YOLO系列的一个延伸&#xff0c;其网络结构共分为&#xff1a;input、backbone、neck和head四个模块&#xff0c;yolov5对yolov4网络的四个部分都进行了修改&#xff0c;并取得了较大的提升&#xff0c;在input端使用了Mosaic数据增强、自适应锚框计算、自…

鸿蒙云函数调试坑点

如果你要本地调试请使用 const {payload, action} event.body/** 本地调试不需要序列化远程需要序列化 */ // const {payload, action} JSON.parse(event.body) const {payload, action} event.body 注意: 只要修改云函数&#xff0c;必须上传云函数 如果使用 const {pay…

牛客NC98 判断t1树中是否有与t2树完全相同的子树【simple 深度优先dfs C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/4eaccec5ee8f4fe8a4309463b807a542 思路 深度优先搜索暴力匹配 思路和算法这是一种最朴素的方法——深度优先搜索枚举 s 中的每一个节点&#xff0c;判断这个点的子树是否和 t 相等。如何判断一个节点的子树是否…

HTTP/1.1,HTTP/2.0和HTTP/3.0 各版本协议的详解(2024-04-24)

1、HTTP介绍 HTTP 协议有多个版本&#xff0c;目前广泛使用的是 HTTP/1.1 和 HTTP/2&#xff0c;以及正在逐步推广的 HTTP/3。 HTTP/1.1&#xff1a;支持持久连接&#xff0c;允许多个请求/响应通过同一个 TCP 连接传输&#xff0c;减少了建立和关闭连接的消耗。 HTTP/2&#…

基于STM32和阿里云的智能台灯(STM32+ESP8266+MQTT+阿里云+语音模块)

一、主要完成功能 1、冷光模式和暖光模式两种灯光 主要支持冷光和暖光模式两种&#xff0c;可以通过语音模块或手机app远程切换冷暖光 2、自动模式和手动模式 主要支持手动模式和自动两种模式&#xff08;app或语音助手切换&#xff09; (1)自动模式&#xff1a;根据环境光照…

vscode 使用文件模板功能来添加版权信息

vscode 新建文件的时候&#xff0c;自动填充作者及版权信息 无需使用插件&#xff0c;操作如下&#xff1a; 选择 “首选项(Preferences)”。在搜索框中输入 “file template” 或者 “文件模板”&#xff0c;然后选择相关的设置项。 {"C_Cpp.clang_format_fallbackSt…

[lesson58]类模板的概念和意义

类模板的概念和意义 类模板 一些类主要用于存储和组织数据元素 类中数据组织的方式和数据元素的具体类型无关 如&#xff1a;数组类、链表类、Stack类、Queue类等 C中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。 C中的类模板…

Docker 开启远程安全访问

说明 如果你的服务器是公网IP&#xff0c;并且开放了docker的远程访问&#xff0c;如果没有进行保护是非常危险的&#xff0c;任何人都可以向你的docker中推送镜像、运行实例。我曾开放过阿里云服务器中docker的远程访问权限&#xff0c;在没有开启保护的状态下&#xff0c;几…

企业微信hook接口协议,根据手机号搜索联系人

根据手机号搜索联系人 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信 请求示例 {"uuid":"3240fde0-45e2-48c0-90e8-cb098d0ebe43","phoneNumber":"1357xxxx" } 返回示例 {"data&q…

抖音 小程序 获取手机号 报错 getPhoneNumber:fail auth deny

这是因为 当前小程序没有获取 手机号的 权限 此能力仅支持小程序通过试运营期后可用&#xff0c;默认获取权限&#xff0c;无需申请&#xff1b; https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/guide/open-capabilities/acquire-phone-number-acqu…

用斐波那契数列感受算法的神奇(21亿耗时0.2毫秒)

目录 一、回顾斐波那契数列 二、简单递归方法 &#xff08;一&#xff09;解决思路 &#xff08;二&#xff09;代码展示 &#xff08;三&#xff09;性能分析 三、采用递归HashMap缓存 &#xff08;一&#xff09;解决思路 &#xff08;二&#xff09;代码展示 &…

深度学习系列65:数字人openHeygen详解

1. 主流程分析 从inference.py函数进入&#xff0c;主要流程包括&#xff1a; 1&#xff09; 使用cv2获取视频中所有帧的列表&#xff0c;如下&#xff1a; 2&#xff09;定义Croper。核心代码为69行&#xff1a;full_frames_RGB, crop, quad croper.crop(full_frames_RGB)。…

公开课学习——基于索引B+树精准建立高性能索引

文章目录 遇到慢查询怎么办&#xff1f;—— 创建索引联合索引的底层的数据存储结构长什么样&#xff1f; mysql脑图 阿里开发手册 遇到慢查询怎么办&#xff1f;—— 创建索引 不用索引的话一个一个找太慢了&#xff0c;用索引就快的多。 假如使用树这样的结构建立索引&#x…

Spring - 3 ( 12000 字 Spring 入门级教程 )

一&#xff1a;Spring Web MVC入门 1.1 响应 在我们前⾯的代码例子中&#xff0c;都已经设置了响应数据, Http 响应结果可以是数据, 也可以是静态页面&#xff0c;也可以针对响应设置状态码, Header 信息等. 1.2 返回静态页面 创建前端页面 index.html(注意路径) html代码 …

frp 实现 http / tcp 内网穿透(穿透 wordpress )

frp 实现 http / tcp 内网穿透&#xff08;穿透 wordpress &#xff09; 1. 背景简介与软件安装2. 服务端配置2.1 配置文件2.2 wordpress 配置文件2.3 frps 自启动 3.客户端配置3.1 配置文件3.2 frpc 自启动 同步发布在个人笔记frp 实现 http / tcp 内网穿透&#xff08;穿透 w…

HZNUCTF -- web

HZNUCTF第五届校赛实践赛初赛 Web方向 WriteUp-CSDN博客 ezssti 下载文件 访问 /login 可由源代码中看到 Eval 函数 &#xff0c;可以任意命令执行 按照格式&#xff0c;可执行命令 POST &#xff1a;name{{.Eval "env"}} 可以得到flag &#xff08;尝试ls 只能列出…