【二十六】【QT开发应用】无边窗窗口自定义标题栏以及利用WindowApi实现拖拉窗口,缩放窗口

设置无边窗属性

MainWidget::MainWidget(QWidget* parent)
	: QWidget(parent) {

	m_nBorderWidth = 5;
	//this->setWindowFlags(Qt::FramelessWindowHint |Qt::WindowMinMaxButtonsHint );
	this->setWindowFlags(Qt::FramelessWindowHint);



	initUI();
}

使用this->setWindowFlags(Qt::FramelessWindowHint);这行代码设置无边窗属性.
但是我们不设置Qt::WindowMinMaxButtonsHint这个属性,如果我们设置了这个属性会导致无法使用window api实现窗口的缩放.

自定义标题栏

在这里插入图片描述
添加新建类作为我们的自定义标题栏.

CTitileBar.h 头文件

#pragma once
#include <QWidget>
#include <QLabel>
#include <QPushButton>
class CTitleBar : public QWidget 
{
	Q_OBJECT

public:
	CTitleBar(QWidget* p = nullptr);
	~CTitleBar();

private:
	void initUI();

private:
	void mousePressEvent(QMouseEvent* event) override;

private:
	QLabel* Label_mpLogo;
	QLabel* Label_mpTitleText;

	QPushButton* Btn_mpSet;
	QPushButton* Btn_mpMin;
	QPushButton* Btn_mpMax;
	QPushButton* Btn_mpClose;

};


void initUI();定义初始化函数用来初始化该类,即自定义标题栏.
void mousePressEvent(QMouseEvent* event) override;重载mousePressEvent函数用来让窗口可以拖动.
对鼠标按下事件进行重写.

在这里插入图片描述

这些私有成员是该标题栏拥有的控件.

CTitileBar.cpp 源文件

#include "CTitleBar.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <qt_windows.h>

CTitleBar::CTitleBar(QWidget* p)
	:QWidget(p)
{
	initUI();
}

CTitleBar::~CTitleBar() {}

void CTitleBar::initUI() {
	setAttribute(Qt::WA_StyledBackground);
	this->setStyleSheet("background-color:rgb(156,156,156)");
	this->setFixedHeight(32);

	Label_mpLogo = new QLabel(this);
	Label_mpTitleText = new QLabel(this);
	Btn_mpSet = new QPushButton(this);
	Btn_mpMin = new QPushButton(this);
	Btn_mpMax = new QPushButton(this);
	Btn_mpClose = new QPushButton(this);

	Label_mpLogo->setFixedSize(24, 24);
	Label_mpTitleText->setText("我是标题");
	Label_mpTitleText->setFixedWidth(120);
	Btn_mpSet->setFixedSize(24, 24);
	Btn_mpMin->setFixedSize(24, 24);
	Btn_mpMax->setFixedSize(24, 24);
	Btn_mpClose->setFixedSize(24, 24);

	QHBoxLayout* Layout_pHTitle = new QHBoxLayout(this);
	Layout_pHTitle->addWidget(Label_mpLogo);
	Layout_pHTitle->addWidget(Label_mpTitleText);
	Layout_pHTitle->addStretch();
	Layout_pHTitle->addWidget(Btn_mpSet);
	Layout_pHTitle->addWidget(Btn_mpMin);
	Layout_pHTitle->addWidget(Btn_mpMax);
	Layout_pHTitle->addWidget(Btn_mpClose);

	Layout_pHTitle->setContentsMargins(5, 5, 5, 5);

}

void CTitleBar::mousePressEvent(QMouseEvent* event) {
	//实现窗口可拖拽
	if (ReleaseCapture()) {
		QWidget* pWindow = this->window();
		if (pWindow->isTopLevel()) {
			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}
	}
}

启用窗口背景样式支持

setAttribute(Qt::WA_StyledBackground); 是一个 Qt 属性,用于启用对窗口背景的样式支持。设置这个属性后,窗口的背景可以通过 Qt 的样式表(QSS)进行自定义,允许更灵活的界面设计。
this->setStyleSheet("background-color:rgb(156,156,156)");设置背景样式.

在这里插入图片描述

实现标题栏拖拉效果

void CTitleBar::mousePressEvent(QMouseEvent* event) {
	//实现窗口可拖拽
	if (ReleaseCapture()) {
		QWidget* pWindow = this->window();
		if (pWindow->isTopLevel()) {
			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}
	}

这段代码实现了一个自定义标题栏的鼠标按下事件,使得用户可以通过拖动标题栏来移动窗口。

  1. 函数定义

    void CTitleBar::mousePressEvent(QMouseEvent* event) {
    

    这是一个重写的鼠标按下事件处理函数,CTitleBar 是自定义的标题栏类。

  2. 释放捕获

    if (ReleaseCapture()) {
    

    ReleaseCapture() 是一个 Windows API 函数,用于释放当前捕获的鼠标。如果当前没有窗口捕获鼠标,函数将返回 0。这个调用在这里主要用于确保能够正确处理后续的拖动操作。

  3. 获取窗口指针

    QWidget* pWindow = this->window();
    

    this->window() 获取当前标题栏的父窗口指针,允许后续对窗口进行操作。

  4. 检查是否为顶层窗口

    if (pWindow->isTopLevel()) {
    

    这一行判断获取的窗口是否是顶层窗口(即直接在桌面上,不是其他窗口的子窗口)。

  5. 发送移动消息

    SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
    
    • SendMessage() 是 Windows API 函数,用于向指定窗口发送消息。
    • HWND(pWindow->winId()) 将 Qt 的窗口 ID 转换为 Windows 的窗口句柄。
    • WM_SYSCOMMAND 是一个消息,用于系统命令,SC_MOVE 表示开始移动窗口。
    • HTCAPTION 是一个常量,表示消息来源于窗口的标题栏。

整体逻辑

  1. 当用户在标题栏上按下鼠标时,调用 mousePressEvent
  2. 通过 ReleaseCapture() 释放当前鼠标捕获,以确保窗口能够正确响应鼠标事件。
  3. 获取当前窗口指针并检查它是否是顶层窗口。
  4. 如果是顶层窗口,通过发送系统命令消息 WM_SYSCOMMAND 和参数 SC_MOVE + HTCAPTION 来通知系统开始移动窗口。

MainWidget.h 头文件

#pragma once

#include <QtWidgets/QWidget>

#include "CTitleBar.h"


class MainWidget : public QWidget
{
    Q_OBJECT

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

private:
    void initUI();

private:
    


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

private:
	int m_nBorderWidth = 10;

    CTitleBar* CTitleBar_mp = nullptr;

private:

};

bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;重写nativeEvent函数用来实现缩放窗口.
int m_nBorderWidth = 10;需要用到的变量.表示位于边界的像素大小为10.
在这里插入图片描述
CTitleBar* CTitleBar_mp = nullptr;表示标题栏.

MainWidget.cpp 源文件

#include "MainWidget.h"
#include "QVBoxLayout"

#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>

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

MainWidget::MainWidget(QWidget* parent)
	: QWidget(parent) {

	m_nBorderWidth = 5;
	//this->setWindowFlags(Qt::FramelessWindowHint |Qt::WindowMinMaxButtonsHint );
	this->setWindowFlags(Qt::FramelessWindowHint);



	initUI();
}


MainWidget::~MainWidget() {}

void MainWidget::initUI() {
	CTitleBar_mp = new CTitleBar(this);
	QWidget* Widget_Main = new QWidget(this);
	Widget_Main->setMinimumSize(600, 400);

	QVBoxLayout* Layout_pVMain = new QVBoxLayout(this);
	Layout_pVMain->addWidget(CTitleBar_mp);
	Layout_pVMain->addWidget(Widget_Main);

	Layout_pVMain->setContentsMargins(0, 0, 0, 0);
	setLayout(Layout_pVMain);
}

bool MainWidget::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) {
	MSG* param = static_cast<MSG*>(message);

	switch (param->message) {
	case WM_NCHITTEST:
	{

		/*int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
		int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();*/

		QPoint globalPos = QCursor::pos(); // 获取鼠标的全局坐标
		QPoint localPos = this->mapFromGlobal(globalPos); // 转换为窗口坐标
		int nX = localPos.x(); // 现在的 nX 应该是相对于窗口的坐标
		int nY = localPos.y();

		//if (childAt(nX, nY) != nullptr)
		//	return QWidget::nativeEvent(eventType, message, result);



		if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
			nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth) {
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

		if ((nX > 0) && (nX < m_nBorderWidth))
			*result = HTLEFT;

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

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

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

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

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

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

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

		return true;



	}
	}

	return false;
}

initUI()初始化函数

void MainWidget::initUI() {
	CTitleBar_mp = new CTitleBar(this);
	QWidget* Widget_Main = new QWidget(this);
	Widget_Main->setMinimumSize(600, 400);

	QVBoxLayout* Layout_pVMain = new QVBoxLayout(this);
	Layout_pVMain->addWidget(CTitleBar_mp);
	Layout_pVMain->addWidget(Widget_Main);

	Layout_pVMain->setContentsMargins(0, 0, 0, 0);
	setLayout(Layout_pVMain);
}

整体窗口是一个竖直布局,竖直布局中一个是标题栏,一个是widget.
在这里插入图片描述

nativeEvent函数重写

bool MainWidget::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) {

这是一个重写的 nativeEvent 函数,用于接收原生的操作系统事件。它返回一个布尔值,指示事件是否被处理。

MSG* param = static_cast<MSG*>(message);

将传入的 message 转换为 Windows 消息结构 MSG,以便后续处理。

switch (param->message) {
case WM_NCHITTEST:

这部分代码处理 WM_NCHITTEST 消息,它用于检测鼠标在窗口非客户区(如边框和标题栏)的位置。

QPoint globalPos = QCursor::pos(); // 获取鼠标的全局坐标
QPoint localPos = this->mapFromGlobal(globalPos); // 转换为窗口坐标
int nX = localPos.x(); // 相对于窗口的 x 坐标
int nY = localPos.y(); // 相对于窗口的 y 坐标
  • QCursor::pos() 获取鼠标的全局坐标。
  • mapFromGlobal() 将全局坐标转换为相对于窗口的坐标。
		if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
			nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth) {
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

这段代码用于检查鼠标位置是否在窗口的客户区内,并且判断该位置是否在任何子窗口上。

  1. 判断鼠标位置是否在客户区内

    if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
        nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth) {
    
    • m_nBorderWidth 是自定义的边框宽度,用于定义边框区域。
    • 这一条件判断鼠标的 X 坐标 nX 是否在边框宽度以外,以及 Y 坐标 nY 是否在边框宽度以外。
    • 如果鼠标在客户区内,条件为真。
  2. 检查子窗口

    if (childAt(nX, nY) != nullptr)
        return QWidget::nativeEvent(eventType, message, result);
    
    • childAt(nX, nY) 检查在 (nX, nY) 位置是否有子窗口。如果该位置上有子窗口,返回 QWidget::nativeEvent(eventType, message, result);,表示事件已经由子窗口处理。
    • 这段逻辑确保如果鼠标指向的是一个子窗口,当前窗口不再处理此事件。

在这里插入图片描述

整体逻辑

  • 当鼠标事件到达窗口时,nativeEvent 函数被调用。
  • 它检查鼠标位置,并判断鼠标是否在窗口的边框或角落。
  • 根据鼠标位置,设置 *result 变量,以指示操作系统如何处理鼠标交互(如拖动或调整大小)。
  • 最终返回值指示事件是否被处理。

错误示例

		int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
		int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();

这样计算出来的nX和nY无法表示鼠标以窗口左上角为原点的位置.

	//this->setWindowFlags(Qt::FramelessWindowHint |Qt::WindowMinMaxButtonsHint );
	this->setWindowFlags(Qt::FramelessWindowHint);

不能设置Qt::WindowMinMaxButtonsHint属性否则无法实现窗口缩放效果,

在这里插入图片描述

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值