Qt开发 | 无边框窗口 | 自定义标题栏 | 拖拽拉伸 | 窗口阴影 | 圆角窗口

文章目录

  • 一、QWidget类介绍
  • 二、无边框窗口的基本实现
  • 三、自定义标题栏并实现拖拽拉伸
  • 四、设计一个无边框窗口公共类
  • 五、标题栏qss美化、关闭、最小化、最大化
  • 六、实现窗口阴影
  • 七、圆角窗口
  • 八、一个自定义标题栏带圆角阴影的窗口

一、QWidget类介绍

  QWidget 是 Qt 框架中的一个核心类,它是所有控件和窗口的基类。QWidget 提供了创建和管理 GUI 组件的基础功能。Qt所有的窗口、控件以及布局都是派生于QWidget。

关键特性与功能

  • 窗口和控件的基类:几乎所有的 GUI 组件都是 QWidget 的子类,包括按钮、文本框、标签、窗口等。
  • 布局管理QWidget 支持多种布局管理方式,如水平布局、垂直布局和网格布局,这些布局可以帮助开发者轻松地组织控件的位置和大小。
  • 事件处理QWidget 可以接收和处理各种事件,如鼠标点击、键盘输入、窗口大小变化等。
  • 绘图功能QWidget 提供了基本的绘图功能,允许开发者在控件上绘制图形和文本。
  • 控件属性QWidget 允许设置控件的各种属性,如背景颜色、边框样式、是否可编辑等。
  • 焦点管理QWidget 可以控制控件的焦点,决定哪些控件可以接收键盘输入。
  • 尺寸和位置QWidget 可以设置控件的大小、位置以及最小和最大尺寸。
  • 子控件管理QWidget 可以添加、移除和查询子控件。
  • 窗口修饰:对于窗口类型的 QWidget,可以设置窗口的标题、图标、状态(如是否可最小化、最大化)等。
  • 资源管理QWidget 管理着与之关联的资源,如字体、颜色、画笔等。
  • 信号和槽QWidget 支持 Qt 的信号和槽机制,允许控件之间的事件通信。
  • 国际化QWidget 支持国际化,可以根据不同地区的设置显示不同的文本。

详细内容可参考https://doc.qt.io/qt-5/qwidget.html

Qt很多问题都可以从QWidget类找到答案,学习Qt大部分就是在与QWidget打交道。

二、无边框窗口的基本实现

  无边框窗口就是在QWidget的基础上去掉窗口边框实现窗口移动(窗口随鼠标做矢量的平移)。

  • 如何去掉窗口边框

    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    
  • 如何实现窗口移动

    去掉窗口边框后,窗口无法移动。需要重写鼠标按下、移动事件函数。如果你在子类中重写了 mousePressEvent 函数,就可以自定义控件对鼠标按下事件的响应。在按下鼠标时,让窗口移动。

    事件处理:鼠标按下事件函数用于接收鼠标按下事件。当用户在控件上按下鼠标按钮时,Qt 框架会调用这个函数。

    //鼠标按下事件函数
    void QWidget::mousePressEvent(QMouseEvent *event) override;
    //鼠标移动事件函数
    void QWidget::mouseMoveEvent(QMouseEvent *event) override;
    
    • QMouseEvent

      该对象包含鼠标事件的详细信息,如鼠标的坐标位置、按下的是哪个鼠标按钮、鼠标事件的类型等。

    移动位置函数:用来移动窗口、控件

    void move(const QPoint&);
    

    要实现窗口随鼠标做矢量平移的效果,需要利用Qt中的一些位置

    • 窗口位置:窗口左上角相对于桌面左上角的位置

      this->pos();
      
    • 鼠标位置

      event->pos();		//鼠标相对于应用程序的位置
      event->globalPos();	//鼠标相对于桌面左上角的位置,绝对位置
      
    • 利用鼠标与窗口左上角的相对位置,实现窗口随坐标的矢量平移

      void MainWidget::mousePressEvent(QMouseEvent* event)
      {
          mouse_pos = event->globalPos();
          window_pos = this->pos();
          diff_pos = mouse_pos - window_pos; //鼠标和窗口的相对位移
      }
      
      void MainWidget::mouseMoveEvent(QMouseEvent* event)
      {
          QPoint pos = event->globalPos();
          //this->move(pos);              //这样移动后窗口的左上角瞬间移动到鼠标处
          this->move(pos - diff_pos);     //这样移动窗口随坐标矢量平移
      }
      

示例:

MainWidget.h

#pragma once

#include <QtWidgets/QWidget>

class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = Q_NULLPTR);

private:
    void mousePressEvent(QMouseEvent* event) override;  //重写鼠标按下事件处理函数
    void mouseMoveEvent(QMouseEvent* event) override;   //重写鼠标移动事件处理函数

private:
    QPoint diff_pos;    //鼠标和窗口的相对位移
    QPoint window_pos;  //窗口的位置
    QPoint mouse_pos;   //鼠标的位置
};

MainWidget.cpp

#include "MainWidget.h"
#include <QMouseEvent>

MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
    //去掉窗口边框
    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
}

void MainWidget::mousePressEvent(QMouseEvent* event)
{
    mouse_pos = event->globalPos();
    window_pos = this->pos();
    diff_pos = mouse_pos - window_pos; //鼠标和窗口的相对位移
}

void MainWidget::mouseMoveEvent(QMouseEvent* event)
{
    QPoint pos = event->globalPos();
    //this->move(pos);              //这样移动后窗口的左上角瞬间移动到鼠标处
    this->move(pos - diff_pos);     //这样移动窗口随坐标矢量平移
}

main.cpp

#include "MainWidget.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;
    w.show();
    return a.exec();
}

三、自定义标题栏并实现拖拽拉伸

  在去掉窗口边框与实现窗口移动后,没有标题栏类,需要自定义标题栏类。创建标题栏界面类,添加到界面上,并添加最小化、最大化,关闭按钮。

  • 无边框窗口拖拽移动更好的解决办法

    #include <qt_windows.h>
    #pragma comment(lib, "user32.lib")
    
    void CTitleBar::mousePressEvent(QMouseEvent* event)	
    {
    	if (ReleaseCapture())
    	{
    		QWidget* pWindow = this->window();
    		if (pWindow->isTopLevel())
    		{
    			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
    		}
    	}
    	event->ignore();
    }
    
  • 无边窗口的拉伸、改变大小

    #include <qt_windows.h>
    #pragma comment(lib, "user32.lib")
    
    
    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);    //无边框窗口
    this->setAttribute(Qt::WA_Hover);	//启用控件悬停效果,配合判断鼠标状态
    
    //通过判断鼠标的状态进行拉伸
    bool MainWidget::nativeEvent(const QByteArray& eventType, void* message, long* result)
    {
    	MSG* param = static_cast<MSG*>(message);
    
    	switch (param->message) //根据 param->message 的值来处理不同类型的消息
    	{
    	case WM_NCHITTEST:	//WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
    	{	
    		//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
    		int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
    		int nY = GET_Y_LPARAM(param->lParam) - this->geometry().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)
    		{
    			//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
    			if (childAt(nX, nY) != nullptr)
    				return QWidget::nativeEvent(eventType, message, result);
    		}
    
    		//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
    		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;
    		//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
    		return true;
    	}
    	}
    	return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
    }
    

示例:

CTitleBar.h

#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>

/**
	自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
public:
	CTitleBar(QWidget* parent = nullptr);
	~CTitleBar();

private:
	void initUI();	//初始化UI
	void mousePressEvent(QMouseEvent* event)override;	//无边框窗口拖拽移动

private:
	QLabel* m_pLogo;
	QLabel* m_pTitleTextLabel;

	QPushButton* m_pSetBtn;
	QPushButton* m_pMinBtn;
	QPushButton* m_pMaxBtn;
	QPushButton* m_pCloseBtn;
};

CTitleBar.cpp

#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>w

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

CTitleBar::~CTitleBar()
{
}

void CTitleBar::initUI()
{
	this->setAttribute(Qt::WA_StyledBackground);    //禁止父窗口影响子窗口样式
	this->setFixedHeight(32 + 10);
	this->setStyleSheet("background-color:rgb(54, 54, 54)");

	m_pLogo = new QLabel(this);
	m_pLogo->setFixedWidth(32);

	m_pTitleTextLabel = new QLabel(this);
	m_pTitleTextLabel->setText(u8"我是标题");
	m_pTitleTextLabel->setFixedWidth(120);

	m_pSetBtn = new QPushButton(this);
	m_pSetBtn->setFixedSize(QSize(32, 32));

	m_pMinBtn = new QPushButton(this);
	m_pMinBtn->setFixedSize(QSize(32, 32));

	m_pMaxBtn = new QPushButton(this);
	m_pMaxBtn->setFixedSize(QSize(32, 32));

	m_pCloseBtn = new QPushButton(this);
	m_pCloseBtn->setFixedSize(QSize(32, 32));

	//布局
	QHBoxLayout* m_pHLay = new QHBoxLayout(this);
	m_pHLay->addWidget(m_pLogo);
	m_pHLay->addWidget(m_pTitleTextLabel);
	m_pHLay->addStretch();	//弹簧
	m_pHLay->addWidget(m_pSetBtn);
	m_pHLay->addWidget(m_pMinBtn);
	m_pHLay->addWidget(m_pMaxBtn);
	m_pHLay->addWidget(m_pCloseBtn);

	//边界间隔
	m_pHLay->setContentsMargins(5, 5, 5, 5);
}

//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)	
{
	//释放当前捕获的鼠标,若成功释放返回true
	if (ReleaseCapture())
	{	
		QWidget* pWindow = this->window();	//获取窗口指针
		if (pWindow->isTopLevel())	//检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
		{
			//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}
	}
	event->ignore();
}

MainWdiget.h

#pragma once

#include <QtWidgets/QWidget>
#include "CTitleBar.h"

class MainWidget : public QWidget
{
    Q_OBJECT

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

private:
    void initUI();  //初始化UI
    bool nativeEvent(const QByteArray& eventType, void* message, long* result)override;


private:
    CTitleBar* m_pTitleBar = nullptr;
    int m_nBorderWidth = 5;
};

MainWdiget.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)
{
    //this->resize()
    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);    //无边框窗口
    this->setAttribute(Qt::WA_Hover);	//启用控件悬停效果,配合判断鼠标状态

    initUI();
}

MainWidget::~MainWidget()
{}

void MainWidget::initUI()
{
    m_pTitleBar = new CTitleBar(this);  //标题栏
    QWidget* w = new QWidget(this);
    w->setMinimumSize(QSize(600, 400));

    QVBoxLayout* pVlay = new QVBoxLayout(this);
    pVlay->addWidget(m_pTitleBar);
    pVlay->addWidget(w);

    pVlay->setContentsMargins(0, 0, 0, 0);  //边界间隙

}

//通过判断鼠标的状态进行拉伸
bool MainWidget::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
	MSG* param = static_cast<MSG*>(message);

	switch (param->message) //根据 param->message 的值来处理不同类型的消息
	{
	case WM_NCHITTEST:	//WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
	{	
		//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
		int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
		int nY = GET_Y_LPARAM(param->lParam) - this->geometry().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)
		{
			//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

		//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
		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;
		//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
		return true;
	}
	}
	return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
}

main.cpp

#include "MainWidget.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;
    w.show();
    return a.exec();
}

运行结果:无边框、可移动、可伸缩

image-20240629203728563

四、设计一个无边框窗口公共类

  抽取代码,新建无边框窗口类CFrameLessWidgetBase,这个类具有可拉伸的特性,然后主窗口继承于这个类。

CFrameLessWidgetBase.h

#pragma once
#include <QtWidgets/QWidget>

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

protected:
	bool nativeEvent(const QByteArray& eventType, void* message, long* result)override;
private:
	int m_nBorderWidth = 5;
};

CFrameLessWidgetBase.cpp

#include "CFrameLessWidgetBase.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")

CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* parent)
{
	this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);    //无边框窗口
	this->setAttribute(Qt::WA_Hover);	//启用控件悬停效果,配合判断鼠标状态
}

CFrameLessWidgetBase::~CFrameLessWidgetBase()
{
}
//通过判断鼠标的状态进行拉伸
bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
	MSG* param = static_cast<MSG*>(message);

	switch (param->message) //根据 param->message 的值来处理不同类型的消息
	{
	case WM_NCHITTEST:	//WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
	{
		//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
		int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
		int nY = GET_Y_LPARAM(param->lParam) - this->geometry().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)
		{
			//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

		//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
		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;
		//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
		return true;
	}
	}
	return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
}

CTitleBar.h

#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>

/**
	自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
public:
	CTitleBar(QWidget* parent = nullptr);
	~CTitleBar();

private:
	void initUI();	//初始化UI
	void mousePressEvent(QMouseEvent* event)override;	//无边框窗口拖拽移动

private:
	QLabel* m_pLogo;
	QLabel* m_pTitleTextLabel;

	QPushButton* m_pSetBtn;
	QPushButton* m_pMinBtn;
	QPushButton* m_pMaxBtn;
	QPushButton* m_pCloseBtn;
};

CTitleBar.cpp

#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>w

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

CTitleBar::~CTitleBar()
{
}

void CTitleBar::initUI()
{
	this->setAttribute(Qt::WA_StyledBackground);    //禁止父窗口影响子窗口样式
	this->setFixedHeight(32 + 10);
	this->setStyleSheet("background-color:rgb(54, 54, 54)");

	m_pLogo = new QLabel(this);
	m_pLogo->setFixedWidth(32);

	m_pTitleTextLabel = new QLabel(this);
	m_pTitleTextLabel->setText(u8"我是标题");
	m_pTitleTextLabel->setFixedWidth(120);

	m_pSetBtn = new QPushButton(this);
	m_pSetBtn->setFixedSize(QSize(32, 32));

	m_pMinBtn = new QPushButton(this);
	m_pMinBtn->setFixedSize(QSize(32, 32));

	m_pMaxBtn = new QPushButton(this);
	m_pMaxBtn->setFixedSize(QSize(32, 32));

	m_pCloseBtn = new QPushButton(this);
	m_pCloseBtn->setFixedSize(QSize(32, 32));

	//布局
	QHBoxLayout* m_pHLay = new QHBoxLayout(this);
	m_pHLay->addWidget(m_pLogo);
	m_pHLay->addWidget(m_pTitleTextLabel);
	m_pHLay->addStretch();	//弹簧
	m_pHLay->addWidget(m_pSetBtn);
	m_pHLay->addWidget(m_pMinBtn);
	m_pHLay->addWidget(m_pMaxBtn);
	m_pHLay->addWidget(m_pCloseBtn);

	//边界间隔
	m_pHLay->setContentsMargins(5, 5, 5, 5);
}

//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)	
{
	//释放当前捕获的鼠标,若成功释放返回true
	if (ReleaseCapture())
	{	
		QWidget* pWindow = this->window();	//获取窗口指针
		if (pWindow->isTopLevel())	//检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
		{
			//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}
	}
	event->ignore();
}

MainWidget.h

#pragma once

#include <QtWidgets/QWidget>
#include "CFrameLessWidgetBase.h"
#include "CTitleBar.h"

class MainWidget : public CFrameLessWidgetBase
{
    Q_OBJECT

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

private:
    void initUI();  //初始化UI
    
private:
    CTitleBar* m_pTitleBar = nullptr;

};

MainWidgwet.cpp

#include "MainWidget.h"
#include <QVBoxLayout>


MainWidget::MainWidget(QWidget *parent)
    : CFrameLessWidgetBase(parent)
{
    //this->resize()
    initUI();
}

MainWidget::~MainWidget()
{}

void MainWidget::initUI()
{
    m_pTitleBar = new CTitleBar(this);  //标题栏
    QWidget* w = new QWidget(this);
    w->setMinimumSize(QSize(600, 400));

    QVBoxLayout* pVlay = new QVBoxLayout(this);
    pVlay->addWidget(m_pTitleBar);
    pVlay->addWidget(w);

    pVlay->setContentsMargins(0, 0, 0, 0);  //边界间隙

}

五、标题栏qss美化、关闭、最小化、最大化

  • qt按钮样式表设置

    //以设置按钮为例
    m_pSetBtn = new QPushButton(this);
    m_pSetBtn->setFixedSize(QSize(32, 32));
    //hover指鼠标悬停在按钮时的样式
    m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/set.svg);border:none}" \
    		"QPushButton:hover{" \
    		"background-color:rgb(99, 99, 99);" \
    		"background-image:url(:/MainWidget/resources/titlebar/set_hover.svg);border:none;}");
    
  • 关闭、最小化、最大化功能实现

    • 方案一:发信号到父窗口,父窗口收到信号后进行相应操作

    • 方案二:在子窗口获取父窗口指针,进行相应操作

      QWidget* pWindow = this->window();
      pWindow->showMinimized();	//最小化
      

      this->window()介绍

      QWidget* QWidget::window() const;
      

      返回此小部件的窗口,即具有(或可能具有)窗口系统框架的下一个祖先小部件。如果小部件是一个窗口,则返回小部件本身。

      槽函数代码为:

      void CTitleBar::onClicked()
      {
      	QPushButton* pBtn = qobject_cast<QPushButton*>(sender());	//获得发送者
      
      	QWidget* pWindow = this->window();	//获取父窗口指针
      	if (pBtn == m_pMinBtn)
      	{
      		pWindow->showMinimized();	//最小化显示
      	}
      	else if (pBtn == m_pMaxBtn)
      	{
      		if (pWindow->isMaximized())	//若已经是最大化,则常规显示
      		{
      			pWindow->showNormal();
      			m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
      				"QPushButton:hover{" \
      				"background-color:rgb(99, 99, 99);" \
      				"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");
      		}
      		else//否则,最大化显示
      		{
      			pWindow->showMaximized();
      			m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/max.svg);border:none}" \
      				"QPushButton:hover{" \
      				"background-color:rgb(99, 99, 99);" \
      				"background-image:url(:/MainWidget/resources/titlebar/max_hover.svg);border:none;}");
      		}
      	}
      	else if (pBtn == m_pCloseBtn)
      	{
      		emit signal_closeBtn();	//向主窗口发送信号来关闭
      	}
      }
      

示例:

CFrameLessWidgetBase.h

#pragma once
#include <QtWidgets/QWidget>

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

protected:
	bool nativeEvent(const QByteArray& eventType, void* message, long* result)override;
private:
	int m_nBorderWidth = 5;
};

CFrameLessWidgetBase.cpp

#include "CFrameLessWidgetBase.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")

CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* parent)
{
	this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);    //无边框窗口
	this->setAttribute(Qt::WA_Hover);	//启用控件悬停效果,配合判断鼠标状态
}

CFrameLessWidgetBase::~CFrameLessWidgetBase()
{
}
//通过判断鼠标的状态进行拉伸
bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
	MSG* param = static_cast<MSG*>(message);

	switch (param->message) //根据 param->message 的值来处理不同类型的消息
	{
	case WM_NCHITTEST:	//WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标点击发生在窗口的哪个部分
	{
		//将鼠标的屏幕坐标转换为相对于控件左上角的坐标
		int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
		int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();

		//这种逻辑是不合理的,因为边框也在控件之上,点击边框会一直调用基类的 nativeEvent 处理事件,而不能伸缩
		/*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)
		{
			//如果鼠标位置下存在子控件,则调用基类的 nativeEvent 处理事件;后续会调用该控件的鼠标点击函数用于拖拽移动
			if (childAt(nX, nY) != nullptr)
				return QWidget::nativeEvent(eventType, message, result);
		}

		//判断鼠标点击是否在控件的边框上,并设置 *result 为相应的值。例如 HTLEFT、HTRIGHT、HTTOP 等,这些值定义了鼠标点击的边框部分。
		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;
		//如果处理了 WM_NCHITTEST 消息,函数返回 true,表示事件已被处理。
		return true;
	}
	}
	return false; //如果没有处理任何消息,函数返回 false,表示事件将被传递给 Qt 进行默认处理。
}

CTitleBar.h

#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>

/**
	自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
	Q_OBJECT
public:
	CTitleBar(QWidget* parent = nullptr);
	~CTitleBar();

private:
	void initUI();	//初始化UI
	void mousePressEvent(QMouseEvent* event)override;	//无边框窗口拖拽移动
	void mouseDoubleClickEvent(QMouseEvent* event)override;	//鼠标双击事件

private slots:
	void onClicked();	//最大最小关闭按钮统一的槽函数

signals:
	void signal_closeBtn();	//点击关闭按钮时向主窗口发送信号

private:
	QLabel* m_pLogo;
	QLabel* m_pTitleTextLabel;

	QPushButton* m_pSetBtn;
	QPushButton* m_pMinBtn;
	QPushButton* m_pMaxBtn;
	QPushButton* m_pCloseBtn;
};

CTitleBar.cpp

#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>w

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

CTitleBar::~CTitleBar()
{
}

void CTitleBar::initUI()
{
	this->setAttribute(Qt::WA_StyledBackground);    //禁止父窗口影响子窗口样式
	this->setFixedHeight(32 + 10);
	this->setStyleSheet("background-color:rgb(54, 54, 54)");

	m_pLogo = new QLabel(this);
	m_pLogo->setFixedWidth(32);
	m_pLogo->setStyleSheet("background-image:url(:/MainWidget/resources/titlebar/title_icon.png);border:none");

	m_pTitleTextLabel = new QLabel(this);
	m_pTitleTextLabel->setText(u8"我是标题");
	m_pTitleTextLabel->setFixedWidth(120);
	m_pTitleTextLabel->setStyleSheet("QLabel{font-family: Microsoft YaHei; \
		font-size:18px; \
		color:#BDC8E2;background-color:rgb(54,54,54);}");

	m_pSetBtn = new QPushButton(this);
	m_pSetBtn->setFixedSize(QSize(32, 32));

	m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/set.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/set_hover.svg);border:none;}");

	m_pMinBtn = new QPushButton(this);
	m_pMinBtn->setFixedSize(QSize(32, 32));
	m_pMinBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/min.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/min_hover.svg);border:none;}");

	m_pMaxBtn = new QPushButton(this);
	m_pMaxBtn->setFixedSize(QSize(32, 32));
	m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");

	m_pCloseBtn = new QPushButton(this);
	m_pCloseBtn->setFixedSize(QSize(32, 32));
	m_pCloseBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/close.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/close_hover.svg);border:none;}");

	//布局
	QHBoxLayout* m_pHLay = new QHBoxLayout(this);
	m_pHLay->addWidget(m_pLogo);
	m_pHLay->addWidget(m_pTitleTextLabel);
	m_pHLay->addStretch();	//弹簧

	m_pHLay->addWidget(m_pSetBtn);
	QSpacerItem* pItem1 = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	m_pHLay->addSpacerItem(pItem1);

	m_pHLay->addWidget(m_pMinBtn);
	QSpacerItem* pItem2 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	m_pHLay->addSpacerItem(pItem2);

	m_pHLay->addWidget(m_pMaxBtn);
	QSpacerItem* pItem3 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	m_pHLay->addSpacerItem(pItem3);   // 弹簧每次使用时得new出来,不能重复使用,否则会报析构错误

	m_pHLay->addWidget(m_pCloseBtn);

	//边界间隔
	m_pHLay->setContentsMargins(5, 5, 5, 5);

	//三个发送者(按钮)链接到同一个槽
	connect(m_pMaxBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
	connect(m_pMinBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
	connect(m_pCloseBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
}

//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)	
{
	//释放当前捕获的鼠标,若成功释放返回true
	if (ReleaseCapture())
	{	
		QWidget* pWindow = this->window();	//获取窗口指针
		if (pWindow->isTopLevel())	//检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
		{
			//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}
	}
	event->ignore();
}

//鼠标双击事件
void CTitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
	m_pMaxBtn->clicked();
}

void CTitleBar::onClicked()
{
	QPushButton* pBtn = qobject_cast<QPushButton*>(sender());	//获得发送者

	QWidget* pWindow = this->window();	//获取父窗口指针
	if (pBtn == m_pMinBtn)
	{
		pWindow->showMinimized();	//最小化显示
	}
	else if (pBtn == m_pMaxBtn)
	{
		if (pWindow->isMaximized())	//若已经是最大化,则常规显示
		{
			pWindow->showNormal();
			m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
				"QPushButton:hover{" \
				"background-color:rgb(99, 99, 99);" \
				"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");
		}
		else//否则,最大化显示
		{
			pWindow->showMaximized();
			m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/max.svg);border:none}" \
				"QPushButton:hover{" \
				"background-color:rgb(99, 99, 99);" \
				"background-image:url(:/MainWidget/resources/titlebar/max_hover.svg);border:none;}");
		}
	}
	else if (pBtn == m_pCloseBtn)
	{
		emit signal_closeBtn();	//向主窗口发送信号来关闭
	}
}

MainWidget.h

#pragma once

#include <QtWidgets/QWidget>
#include "CFrameLessWidgetBase.h"
#include "CTitleBar.h"

class MainWidget : public CFrameLessWidgetBase
{
    Q_OBJECT

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

private:
    void initUI();  //初始化UI

private slots:
    void on_closeSlot();
    
private:
    CTitleBar* m_pTitleBar = nullptr;
};

MainWidget.cpp

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


MainWidget::MainWidget(QWidget *parent)
    : CFrameLessWidgetBase(parent)
{
    //this->resize()
    initUI();

    //标题栏中的关闭按钮链接槽函数
    connect(m_pTitleBar, &CTitleBar::signal_closeBtn, this, &MainWidget::on_closeSlot);
}

MainWidget::~MainWidget()
{}

void MainWidget::initUI()
{
    m_pTitleBar = new CTitleBar(this);  //标题栏
    QWidget* w = new QWidget(this);
    w->setMinimumSize(QSize(600, 400));

    QVBoxLayout* pVlay = new QVBoxLayout(this);
    pVlay->addWidget(m_pTitleBar);
    pVlay->addWidget(w);

    pVlay->setContentsMargins(0, 0, 0, 0);  //边界间隙

}

void MainWidget::on_closeSlot()
{
    //窗口关闭处理
    QMessageBox::StandardButton _exit = QMessageBox::warning(this, u8"提示", u8"确定要退出吗", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
    if (_exit == QMessageBox::Yes)
    {
        close();
    }
}

main.cpp

#include "MainWidget.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;
    w.show();
    return a.exec();
}

运行结果

  无边框窗口、可移动、可伸缩、标题栏样式表、最大化、最小化、关闭、双击标题栏可最大化与正常显示。

image-20240629224659047

六、实现窗口阴影

  要实现窗口阴影,需要设置两层窗口

  • 将底层窗口设置为窗体半透明、无边框

    //设置底层窗体背景为半透明
    this->setAttribute(Qt::WA_TranslucentBackground, true);
    
    //设置无边框
    this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    
  • 将顶层窗口设置背景颜色

    //给顶层widget设置背景颜色,不然看不见,因为底层widget已经透明了
    pRealWidget->setStyleSheet("background-color:rgb(255, 254, 253)");
    
  • 新建Qt窗口阴影类,给顶层QWidget设置阴影

    QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
    
    //设置阴影距离
    shadow->setOffset(0, 0);
    //设置阴影颜色  686868
    shadow->setColor(QColor("#252a2b"));	//深灰色
    //设置阴影区域
    shadow->setBlurRadius(30);
    
    //给顶层QWidget设置阴影
    pRealWidget->setGraphicsEffect(shadow);
    

示例:

CLoginDlg.h

#pragma once

#include <QDialog>


class CLoginDlg : public QDialog
{
	Q_OBJECT

public:
	CLoginDlg(QWidget* parent = Q_NULLPTR);
	~CLoginDlg();

private:
	void mousePressEvent(QMouseEvent* event) override;
	void mouseMoveEvent(QMouseEvent* event) override;

private:
	//记录鼠标,窗口位置
	QPoint windowPos;
	QPoint mousePos;
	QPoint dPos;
};

CLoginDlg.cpp

#include "CLoginDlg.h"
#include "CLoginRealWidget.h"
#include <QGraphicsDropShadowEffect>
#include <QVboxLayout>
#include <QMouseEvent>


CLoginDlg::CLoginDlg(QWidget* parent)
    : QDialog(parent)
{
    //设置窗体的背景为半透明
    this->setAttribute(Qt::WA_TranslucentBackground, true);

    //设置无边框
    this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);

    QVBoxLayout* pMainLay = new QVBoxLayout(this);
    CLoginRealWidget* pRealWidget = new CLoginRealWidget(this);
    pMainLay->addWidget(pRealWidget);
    pMainLay->setContentsMargins(30, 30, 30, 30);
    setLayout(pMainLay);

    //给顶层widget设置背景颜色,不然看不见,因为底层widget已经透明了
    pRealWidget->setStyleSheet("background-color:rgb(255, 254, 253)");

    QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);

    //设置阴影距离
    shadow->setOffset(0, 0);
    //设置阴影颜色  686868
    shadow->setColor(QColor("#686868")); //灰色
    //设置阴影区域
    shadow->setBlurRadius(30);

    //给顶层QWidget设置阴影
    pRealWidget->setGraphicsEffect(shadow);
}

CLoginDlg::~CLoginDlg()
{
}

void CLoginDlg::mousePressEvent(QMouseEvent* event)
{
    this->windowPos = this->pos();       // 获得部件当前位置
    this->mousePos = event->globalPos(); // 获得鼠标位置
    this->dPos = mousePos - windowPos;   // 移动后部件所在的位置
}

void CLoginDlg::mouseMoveEvent(QMouseEvent* event)
{
    this->move(event->globalPos() - this->dPos);
}

CLoginRealWidget.h

#pragma once
#include <QWidget>


class CLoginRealWidget : public QWidget
{
	Q_OBJECT

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

};

CLoginRealWidget.cpp

#include "CLoginRealWidget.h"
#include <QLabel>
#include <QLineEdit>
#include <QCheckBox>
#include <QPushButton>
#include <QHBoxLayout>
#include <QGridLayout>

CLoginRealWidget::CLoginRealWidget(QWidget* p)
	:QWidget(p)
{
	//禁止父窗口影响子窗口样式
	setAttribute(Qt::WA_StyledBackground);
	setWindowFlags(Qt::FramelessWindowHint);

	//头像
	QLabel* pImageLabel = new QLabel(this);
	QPixmap pixmap(":/CLoginDlg/resource/user_image.png");
	pImageLabel->setFixedSize(150, 150);
	pImageLabel->setPixmap(pixmap);
	pImageLabel->setScaledContents(true);

	//用户名
	QLineEdit* pUserNameLineEdit = new QLineEdit(this);
	pUserNameLineEdit->setFixedSize(300, 50);
	pUserNameLineEdit->setPlaceholderText(QStringLiteral("QQ号码/手机/邮箱"));

	//密码
	QLineEdit* pPasswordLineEdit = new QLineEdit(this);
	pPasswordLineEdit->setFixedSize(300, 50);
	pPasswordLineEdit->setPlaceholderText(QStringLiteral("密码"));
	pPasswordLineEdit->setEchoMode(QLineEdit::Password);

	QPushButton* pForgotButton = new QPushButton(this);
	pForgotButton->setText(QStringLiteral("找回密码"));
	pForgotButton->setFixedWidth(80);

	QCheckBox* pRememberCheckBox = new QCheckBox(this);
	pRememberCheckBox->setText(QStringLiteral("记住密码"));

	QCheckBox* pAutoLoginCheckBox = new QCheckBox(this);
	pAutoLoginCheckBox->setText(QStringLiteral("自动登录"));

	QPushButton* pLoginButton = new QPushButton(this);
	pLoginButton->setFixedHeight(48);
	pLoginButton->setText(QStringLiteral("登录"));

	QPushButton* pRegisterButton = new QPushButton(this);
	pRegisterButton->setFixedHeight(48);
	pRegisterButton->setText(QStringLiteral("注册账号"));

	QHBoxLayout* pMainLay = new QHBoxLayout(this);

	QSpacerItem* pHSpacer1 = new QSpacerItem(25, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	pMainLay->addSpacerItem(pHSpacer1);

	QGridLayout* pGridLayout = new QGridLayout(this);

	// 头像 第0行,第0列开始,占3行1列
	pGridLayout->addWidget(pImageLabel, 0, 0, 3, 1);

	// 用户名输入框 第0行,第1列开始,占1行2列
	pGridLayout->addWidget(pUserNameLineEdit, 0, 1, 1, 2);

	// 密码输入框 第1行,第1列开始,占1行2列
	pGridLayout->addWidget(pPasswordLineEdit, 1, 1, 1, 2);

	// 忘记密码 第2行,第1列开始,占1行1列
	pGridLayout->addWidget(pForgotButton, 2, 1, 1, 1);

	// 记住密码 第2行,第2列开始,占1行1列 水平居中 垂直居中
	pGridLayout->addWidget(pRememberCheckBox, 2, 2, 1, 1, Qt::AlignLeft | Qt::AlignVCenter);

	// 自动登录 第2行,第2列开始,占1行1列 水平居右 垂直居中
	pGridLayout->addWidget(pAutoLoginCheckBox, 2, 2, 1, 1, Qt::AlignRight | Qt::AlignVCenter);

	// 登录按钮 第3行,第1列开始,占1行2列
	pGridLayout->addWidget(pLoginButton, 3, 1, 1, 2);

	// 注册按钮 第4行,第1列开始,占1行2列
	pGridLayout->addWidget(pRegisterButton, 4, 1, 1, 2);

	// 设置水平间距
	pGridLayout->setHorizontalSpacing(10);

	// 设置垂直间距
	pGridLayout->setVerticalSpacing(10);

	pMainLay->addLayout(pGridLayout);
	QSpacerItem* pHSpacer2 = new QSpacerItem(25, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	pMainLay->addSpacerItem(pHSpacer2);

	pMainLay->setContentsMargins(5, 5, 5, 5);
	setLayout(pMainLay);
}

CLoginRealWidget::~CLoginRealWidget()
{
}

main.cpp

#include "CLoginDlg.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    CLoginDlg w;
    w.show();
    return a.exec();
}

运行结果

image-20240630001329337

七、圆角窗口

  圆角实现有两种方案,前提是:

this->setAttribute(Qt::WA_TranslucentBackground);   //设置窗口背景透明
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);    //设置无边框窗口
  • 方案一:重写paintEvent方法

    void MainWidget::paintEvent(QPaintEvent * event)
    {
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);  //反锯齿
        painter.setBrush(QBrush(QColor(155, 54, 54)));
        painter.setPen(Qt::transparent);
        QRect rect = this->rect();
        painter.drawRoundedRect(rect, 15, 15); //设置窗口圆角15px
    }
    
  • 方案二:样式表qss

    this->setStyleSheet("QWidget{background-color:gray;border-radius:30px;}");
    
    //窗口透明后,需要重写该方法,不然窗口无法显示
    void MainWidget::paintEvent(QPaintEvent* event)
    {
        QStyleOption opt; //QStyleOption 用于传递绘制项的配置选项给样式函数
        opt.init(this);  //表示样式选项与当前的 MainWidget 相关联
    
        QPainter p(this);
        style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); //调用当前样式的 drawPrimitive 方法来绘制一个基本的绘制项
        QWidget::paintEvent(event); //调用基类 QWidget 的 paintEvent 方法
    }
    

    样式表qss可以设置更多圆角样式

    • 左上角:border-top-left-radius:15px
    • 右上角:border-top-right-radius:15px
    • 左下角:border-bottom-left-radius:15px
    • 右下角:border-bottom-right-radius:15px

    border-radius的参数设置

    • 可以只传一个参数,这样x,y方向的圆度是一样的,例如:border-radius:30px
    • 传两个参数:border-radius:15px 20px
      • 第一个参数设置X轴方向的半径
      • 第二个参数设置Y轴方向的半径

八、一个自定义标题栏带圆角阴影的窗口

  需要设计双层窗口,底层为透明窗口,顶层是放控件的窗口。

示例:

CFrameLessWidgetBase.h

/*
无边框窗口基类--可拉伸
其它窗口派生于该类即可
*/
#pragma once
#include <QWidget>

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

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

private:
	int mouse_margin = 5;
};

CFrameLessWidgetBase.cpp

#include "CFrameLessWidgetBase.h"

#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <Windows.h>
#include <windowsx.h>
#endif

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

CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* parent)
{
	setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
	setAttribute(Qt::WA_Hover);
}

CFrameLessWidgetBase::~CFrameLessWidgetBase()
{
}

bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* 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();

		// 如果鼠标位于内部子控件上,则不进行处理
		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);
}

CTitleBar.h

#pragma once
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPushButton>

/**
	自定义标题栏界面类,添加设置、最小化、最大化,关闭按钮
*/
class CTitleBar : public QWidget
{
	Q_OBJECT
public:
	CTitleBar(QWidget* parent = nullptr);
	~CTitleBar();

private:
	void initUI();	//初始化UI
	void paintEvent(QPaintEvent* event) override;
	void mousePressEvent(QMouseEvent* event)override;	//无边框窗口拖拽移动
	void mouseDoubleClickEvent(QMouseEvent* event)override;	//鼠标双击事件

private slots:
	void onClicked();	//最大最小关闭按钮统一的槽函数

signals:
	void sig_close();
	void sig_max(bool isMax);

private:
	QLabel* m_pLogo;
	QLabel* m_pTitleTextLabel;

	QPushButton* m_pSetBtn;
	QPushButton* m_pMinBtn;
	QPushButton* m_pMaxBtn;
	QPushButton* m_pCloseBtn;
};

CTitleBar.cpp

#pragma comment(lib, "user32.lib")
#include "CTitleBar.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QStyleOption>
#include <QPainter>

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

CTitleBar::~CTitleBar()
{
}

void CTitleBar::initUI()
{
	this->setAttribute(Qt::WA_StyledBackground);    //禁止父窗口影响子窗口样式
	this->setFixedHeight(32 + 10);
	//左上,右上圆角10;左下,右下圆角0
	this->setStyleSheet("QWidget{background-color:rgb(54,54,54); \
        border-top-left-radius:10px; \
        border-top-right-radius:10px; \
		border-bottom-left-radius:0px; \
		border-bottom-right-radius:0px;}");

	m_pLogo = new QLabel(this);
	m_pLogo->setFixedWidth(32);
	m_pLogo->setStyleSheet("background-image:url(:/MainWidget/resources/titlebar/title_icon.png);border:none");

	m_pTitleTextLabel = new QLabel(this);
	m_pTitleTextLabel->setText(u8"我是标题");
	m_pTitleTextLabel->setFixedWidth(120);
	m_pTitleTextLabel->setStyleSheet("QLabel{font-family: Microsoft YaHei; \
		font-size:18px; \
		color:#BDC8E2;background-color:rgb(54,54,54);}");

	m_pSetBtn = new QPushButton(this);
	m_pSetBtn->setFixedSize(QSize(32, 32));
	m_pSetBtn->setText("");
	m_pSetBtn->setFlat(true);
	m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/set.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/set_hover.svg);border:none;}");

	m_pMinBtn = new QPushButton(this);
	m_pMinBtn->setFixedSize(QSize(32, 32));
	m_pMinBtn->setText("");
	m_pMinBtn->setFlat(true);
	m_pMinBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/min.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/min_hover.svg);border:none;}");

	m_pMaxBtn = new QPushButton(this);
	m_pMaxBtn->setFixedSize(QSize(32, 32));
	m_pMaxBtn->setText("");
	m_pMaxBtn->setFlat(true);
	m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/normal_hover.svg);border:none;}");

	m_pCloseBtn = new QPushButton(this);
	m_pCloseBtn->setFixedSize(QSize(32, 32));
	m_pCloseBtn->setText("");
	m_pCloseBtn->setFlat(true);
	m_pCloseBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/close.svg);border:none}" \
		"QPushButton:hover{" \
		"background-color:rgb(99, 99, 99);" \
		"background-image:url(:/MainWidget/resources/titlebar/close_hover.svg);border:none;}");

	//布局
	QHBoxLayout* m_pHLay = new QHBoxLayout(this);
	m_pHLay->addWidget(m_pLogo);
	m_pHLay->addWidget(m_pTitleTextLabel);
	m_pHLay->addStretch();	//弹簧

	m_pHLay->addWidget(m_pSetBtn);
	QSpacerItem* pItem1 = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	m_pHLay->addSpacerItem(pItem1);

	m_pHLay->addWidget(m_pMinBtn);
	QSpacerItem* pItem2 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	m_pHLay->addSpacerItem(pItem2);

	m_pHLay->addWidget(m_pMaxBtn);
	QSpacerItem* pItem3 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
	m_pHLay->addSpacerItem(pItem3);   // 弹簧每次使用时得new出来,不能重复使用,否则会报析构错误

	m_pHLay->addWidget(m_pCloseBtn);

	//边界间隔
	m_pHLay->setContentsMargins(5, 5, 5, 5);

	//三个发送者(按钮)链接到同一个槽
	connect(m_pMaxBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
	connect(m_pMinBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
	connect(m_pCloseBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
}

void CTitleBar::paintEvent(QPaintEvent* event)
{
	//决定样式表是否起作用
	//QStyleOption opt;
	//opt.init(this);
	//QPainter p(this);
	//style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
	//QWidget::paintEvent(event);
}

//处理鼠标按下事件--无边框窗口拖拽移动的解决办法
void CTitleBar::mousePressEvent(QMouseEvent* event)	
{
	//释放当前捕获的鼠标,若成功释放返回true
	if (ReleaseCapture())
	{	
		QWidget* pWindow = this->window();	//获取窗口指针
		if (pWindow->isTopLevel())	//检查获取到的窗口是否是一个顶级窗口(即不是其他窗口的子控件)
		{
			//如果窗口是顶级窗口,使用 SendMessage 函数向父窗口句柄发送一个 WM_SYSCOMMAND 消息,该消息指示窗口开始移动操作。
			SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}
	}
	event->ignore();
}

//鼠标双击事件
void CTitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
	m_pMaxBtn->clicked();
}

void CTitleBar::onClicked()
{
	QPushButton* pBtn = qobject_cast<QPushButton*>(sender());	//获得发送者

	QWidget* pWindow = this->window();	//获取父窗口指针
	if (pBtn == m_pMinBtn)
	{
		pWindow->showMinimized();	//最小化显示
	}
	else if (pBtn == m_pMaxBtn)
	{
		if (pWindow->isMaximized())	//若已经是最大化,则常规显示
		{
			pWindow->showNormal();
			m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/normal.svg);border:none; \
					background-position:center; \
					background-repeat:no-repeat;}  \
					QPushButton:hover{ \
					background-color:rgb(99, 99, 99); \
					background-image:url(:/MainWidget/resource/titlebar/normal_hover.svg);border:none;}");
			emit sig_max(false);
		}
		else//否则,最大化显示
		{
			pWindow->showMaximized();
			m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/MainWidget/resources/titlebar/max.svg);border:none; \
					background-position:center; \
					background-repeat:no-repeat;}  \
					QPushButton:hover{ \
					background-color:rgb(99, 99, 99);  \
					background-image:url(:/MainWidget/resource/titlebar/max_hover.svg);border:none;}");
			emit sig_max(true);
		}
	}
	else if (pBtn == m_pCloseBtn)
	{
		emit sig_close();
	}
}

CTopWidget.h

#pragma once
#include <QWidget>

class CTopWidget : public QWidget
{
	Q_OBJECT

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

signals:
	void sig_close();
	void sig_max(bool isMax);

private:
	void paintEvent(QPaintEvent* event) override;
};

CTopWidget.cpp

#include "CTopWidget.h"
#include "CTitleBar.h"
#include <QVBoxLayout>
#include <QStyleOption>
#include <QPainter>

CTopWidget::CTopWidget(QWidget* p)
    :QWidget(p)
{
    setAttribute(Qt::WA_StyledBackground); //禁止父窗口影响子窗口样式
    this->setAttribute(Qt::WA_TranslucentBackground);   //设置窗口背景透明
    //this->setStyleSheet("QWidget{background-color:rgb(255, 254, 253);border-radius:10px;}");
    /*this->setStyleSheet("QWidget{background-color:rgb(255, 254, 253); \
        border-bottom-left-radius:15px; \
        border-bottom-right-radius:15px;}");*/

    QVBoxLayout* pVLay = new QVBoxLayout(this);
    CTitleBar* pTitle = new CTitleBar(this);
    QWidget* pWidget = new QWidget(this);
    pWidget->setStyleSheet("QWidget{background-color:rgb(255, 254, 253); \
    border-bottom-left-radius:15px; \
    border-bottom-right-radius:15px;}");

    //需要指定pWidget最小尺寸,或最大尺寸
    pWidget->setMinimumSize(1200, 800);

    pVLay->addWidget(pTitle);
    pVLay->addWidget(pWidget);
    pVLay->setContentsMargins(0, 0, 0, 0);
    setLayout(pVLay);

    connect(pTitle, &CTitleBar::sig_close, this, &CTopWidget::sig_close);
    connect(pTitle, &CTitleBar::sig_max, this, &CTopWidget::sig_max);
}

CTopWidget::~CTopWidget()
{
}

void CTopWidget::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);  //反锯齿
    painter.setBrush(QBrush(QColor(255, 254, 253)));
    painter.setPen(Qt::transparent);
    QRect rect = this->rect();
    painter.drawRoundedRect(rect, 10, 10); //设置窗口圆角15px
}

MainWidget.h

#pragma once

#include <QtWidgets/QWidget>
#include "CFrameLessWidgetBase.h"
#include "CTopWidget.h"
#include <QVBoxLayout>
#include <QGraphicsDropShadowEffect>

//派生于CFrameLessWidgetBase
class MainWidget : public CFrameLessWidgetBase
{
    Q_OBJECT

public:
    MainWidget(QWidget* parent = Q_NULLPTR);

private slots:
    void onClose();
    void onDoMax(bool isMax);

private:
    QVBoxLayout* m_pMainVLay = nullptr;
    CTopWidget* m_pTopWidget = nullptr;
    QGraphicsDropShadowEffect* m_pShadow = nullptr;
};

MainWidget.cpp

#include "MainWidget.h"


MainWidget::MainWidget(QWidget* parent)
    : CFrameLessWidgetBase(parent)
{
    //设置窗体透明
    this->setAttribute(Qt::WA_TranslucentBackground, true);

    m_pMainVLay = new QVBoxLayout(this);
    m_pTopWidget = new CTopWidget();
    m_pMainVLay->addWidget(m_pTopWidget);

    int shadow_width = 30;
    m_pMainVLay->setContentsMargins(shadow_width, shadow_width, shadow_width, shadow_width);
    setLayout(m_pMainVLay);

    //给顶层widget设置背景颜色,不然看不见,因为底层widget已经透明了
    m_pTopWidget->setStyleSheet("background-color:rgb(255, 254, 253)");
    m_pShadow = new QGraphicsDropShadowEffect(this);

    //设置阴影距离
    m_pShadow->setOffset(0, 0);

    //设置阴影颜色  686868
    m_pShadow->setColor(QColor("#686868"));

    //设置阴影区域
    m_pShadow->setBlurRadius(shadow_width - 5);

    //给顶层QWidget设置阴影
    m_pTopWidget->setGraphicsEffect(m_pShadow);

    connect(m_pTopWidget, &CTopWidget::sig_close, this, &MainWidget::onClose);
    connect(m_pTopWidget, &CTopWidget::sig_max, this, &MainWidget::onDoMax);
}

void MainWidget::onClose()
{
    close();
}

void MainWidget::onDoMax(bool isMax)
{
    int shadow_width = 25;
    if (isMax)
    {
        shadow_width = 0;
    }
    else
    {

    }

    m_pMainVLay->setContentsMargins(shadow_width, shadow_width, shadow_width, shadow_width);

    //设置阴影区域
    m_pShadow->setBlurRadius(shadow_width);

    //给顶层QWidget设置阴影
    m_pTopWidget->setGraphicsEffect(m_pShadow);
}

main.cpp

#include "MainWidget.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;
    w.show();
    return a.exec();
}

运行结果

image-20240630164412822

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合MongoDB JPA使用

一、整合MongoDB SpringDataMongoDB是 SpringData家族成员之一&#xff0c;MongoDB的持久层框架&#xff0c;底层封装了 mongodb-driver。mongodb-driver 是 MongoDB官方推出的 Java连接 MongoDB的驱动包&#xff0c;相当于JDBC驱动。 SpringBoot整合 MongoDB&#xff0c;引入…

【MySQL】数据库——备份与恢复,日志管理1

一、数据备份的重要性 1.备份的主要目的是灾难恢复 在生产环境中&#xff0c;数据的安全性至关重要 任何数据的丢失都可能产生严重的后果造成数据丢失的原因&#xff1a; 程序错误人为,操作错误运算错误磁盘故障灾难&#xff08;如火灾、地震&#xff09;和盗窃 2.数据库备份…

pcap包常见拆分方法

文章目录 Wireshark 拆分流量包SplitCap使用简介魔数报错示例结果 在进行流量分析时&#xff0c;经常需要分析pcap流量包。但是体积过大的流量包不容易直接分析&#xff0c;经常需要按照一定的规则把它拆分成小的数据包。 这里统一选择cic数据集里的Thursday-WorkingHours.pcap…

【Oracle篇】逻辑备份工具expdp(exp)/impdp(imp)和物理备份工具rman的区别和各自的使用场景总汇(第八篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

基于局域网下的服务器连接、文件传输以及内网穿透教程 | 服务器连接ssh | 服务器文件传输scp | 内网穿透frp | 研究生入学必备 | 深度学习必备

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本篇博客分享的是基于局域网下的服务器连接&#x1f517;、文件传输以及内网穿透教程&#xff0c;内容非常完备✨&#xff0c;涵盖了在服务器上做深度学…

目标检测常用涨点方法:注意力机制小结(空间注意力、通道注意力、CBAM等)

1.通道注意力 通道注意力&#xff08;Channel Attention&#xff09;是在通道维度上对输入数据进行学习&#xff0c;再对不同的通道分配相应的权重表示重要性&#xff0c;从而达到“分配注意力”的效果。SENet&#xff08;Squeeze and Excitation networks) 是一个典型的使用通…

J020_二分查找算法

一、查找过程 使用二分查找算法有一个必要的前提&#xff1a;数组已经是一个排好序的数组。 以下面数组为例&#xff0c;讲述二分查找过程&#xff1a; 二、代码实现 package com.itheima.sort;public class BinarySearch {public static void main(String[] args) {int[] a…

STM32第十三课:DMA多通道采集光照烟雾

文章目录 需求一、DMA&#xff08;直接存储器存取&#xff09;二、实现流程1.时钟使能2.设置外设寄存器地址3.设置存储器地址4.设置要传输的数据量5.设置通道优先级6.设置传输方向7.使通道和ADC转换 三、数据处理四、需求实现总结 需求 通过DMA实现光照强度和烟雾浓度的多通道…

VQVAE:Neural Discrete Representation Learning

论文名称&#xff1a;Neural Discrete Representation Learning 开源地址 发表时间&#xff1a;NIPS2017 作者及组织&#xff1a;Aaron van den Oord,Oriol Vinyals和Koray Kavukcuoglu, 来自DeepMind。 1、VAE 简单回顾下VAE的损失函数&#xff0c;ELBO的下界为&#xff1a; …

单晶层状氧化物制作方法技术资料 纳离子技术

网盘 https://pan.baidu.com/s/1hjHsXvTXG74-0fDo5TtXWQ?pwd10jk 单晶型高熵普鲁士蓝正极材料及其制备方法与应用.pdf 厘米级铬氧化物单晶及其制备方法和存储器件.pdf 多孔氧化物单晶材料及其制备方法和应用.pdf 大单晶层状氧化物正极材料及其制备方法和应用.pdf 富钠P2相层状…

3DMAX折纸插件FoldPoly使用方法详解

3DMAX折纸插件FoldPoly使用教程 3DMAX折纸插件FoldPoly&#xff0c;用于挤出可编辑多边形的边&#xff08;边界&#xff09;并可旋转&#xff08;折叠&#xff09;新生成的面&#xff0c;创建类似手工折纸以及纸箱包装盒的建模效果。 【版本要求】 3dMax2014 - 2025&#xff…

线性代数|机器学习-P20鞍点和极值

文章目录 1 . 瑞利商的思考1.1 瑞利商的定义1.2 投影向量 2. 拉格朗日乘子法3. 鞍点 1 . 瑞利商的思考 1.1 瑞利商的定义 假设A是n阶实对称矩阵&#xff0c;x是n维非零列向量&#xff0c;那么瑞利商表示如下&#xff1a; R ( A , x ) x T A x x T x \begin{equation} R(A,x…

嵌入式学习——硬件(ARM内核汇编指令)——day52

ARM汇编指令 学习arm汇编的主要目的是为了编写arm启动代码&#xff0c;启动代码启动以后&#xff0c;引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有&#xff1a; 初始化异常向量表&#xff1b;初…

前端学习 Vue 插槽如何实现组件内容分发?

目录 一、Vue.js框架介绍二、什么是Vue 插槽三、Vue 插槽的应用场景四、Vue 插槽如何实现组件内容分发 一、Vue.js框架介绍 Vue.js是一个用于构建用户界面的渐进式JavaScript框架。它设计得非常灵活&#xff0c;可以轻松地被集成到现有的项目中&#xff0c;也可以作为一个完整…

Redis-实战篇-缓存击穿问题及解决方案

文章目录 1、缓存击穿2、常见的解决方案有两种&#xff1a;2.1、互斥锁2.2、逻辑过期2.3、两种方案对比 3、利用互斥锁解决缓存击穿问题3.1、ShopServiceImpl.java3.2、使用 jmeter.bat 测试高并发 4、利用逻辑过期解决缓存击穿问题 1、缓存击穿 缓存击穿问题 也叫 热点key问题…

半个月从几十升粉到500(发红包喽)

目录 1. 背景2. 涨粉秘籍2.1 持续创作高质量内容2.1.1 保持频率2.1.2 技术文章为主2.1.3 图文并茂 2.2 积极参与社区活动2.2.1 社区分享2.2.2 发文活动 2.3 互动与建立信任2.3.1 与读者互动2.3.2 红包互动2.3.3 动态分享 2.4 标题与内容的优化2.4.1 标题吸引2.4.2 内容实用 2.5…

【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

【从零开始学架构 架构基础】五 架构设计的复杂度来源:低成本、安全、规模

架构设计的复杂度来源其实就是架构设计要解决的问题&#xff0c;主要有如下几个&#xff1a;高性能、高可用、可扩展、低成本、安全、规模。复杂度的关键&#xff0c;就是新旧技术之间不是完全的替代关系&#xff0c;有交叉&#xff0c;有各自的特点&#xff0c;所以才需要具体…

解决idea中git无法管理项目中所有需要管理的文件

点击文件->设置 选择版本控制—>目录映射 点击加号 设置整个项目被Git管理

springboot助农电商系统-计算机毕业设计源码08655

摘要 近年来&#xff0c;电子商务的快速发展引起了行业和学术界的高度关注。基于移动端的助农电商系统旨在为用户提供一个简单、高效、便捷的农产品购物体验&#xff0c;它不仅要求用户清晰地查看所需信息&#xff0c;而且还要求界面设计精美&#xff0c;使得功能与页面完美融…