Qt无边框窗口拖拽和阴影

先看下效果:

说明

自定义窗口控件的无边框,窗口事件由于没有系统自带边框,无法实现拖拽拉伸等事件的处理,一种方法就是重新重写主窗口的鼠标事件,一种时通过nativeEvent事件处理。重写事件相对繁琐,我们这里推荐nativeEvent处理。注意后续我们在做win平台的进程通信,也会用到它!

  • 我们这里使用的是:nativeEvent

软件用到的样式表,这里就不展示了,大家可以自行调整!

关键点说明

QPainterPath

QPainterPath类提供一个容器,可以用来创建图形并且重复使用。绘制器路径是由许多图形构建基块(如矩形、椭圆形、直线和曲线)组成的对象。构建基块可以连接在封闭的子路径中,例如作为矩形或椭圆。封闭路径具有重合的起点和终点。或者它们可以作为未闭合的子路径独立存在,例如直线和曲线。

抗锯齿

  1. 抗锯齿是一种常见的图形处理技术,用于减少在显示器上呈现的图像中出现的锯齿状边缘。
    抗锯齿技术通过在边缘周围添加额外的像素来平滑边缘,从而减少锯齿状边缘。这种技术基于亚像素级别的渲染,它将颜色逐渐混合到边缘像素的周围像素中,使得边缘更加平滑。
  2. 打开抗锯齿可以使图像更加平滑,尤其是在呈现锐利直线或曲线时。这种技术可以减少锯齿状边缘,使得图像更加清晰,更加真实。特别是在高分辨率屏幕上,抗锯齿可以使得字体更加易读,图像更加细腻。
  3. 虽然抗锯齿可以使图像更加平滑,但在某些情况下,关闭抗锯齿可能更加合适。关闭抗锯齿可以提高图像处理速度。
  4. 这里我们基于Qt绘图框架用的是:
  • setRenderHint(QPainter::Antialiasing, true); //打开抗锯齿
  • setRenderHint(QPainter::Antialiasing, false); //关闭抗锯齿

具体实现

CDlgComBase,无边框窗口,带阴影,支持拖拽,注意:

  • 该实现方案不支持存在多个显示屏的情况!
  • 该实现方案仅支持win平台!

实现无边框带阴影的窗口代码,下面的代码供大家参考:

DlgComBase.h

cpp 复制代码
#pragma once
#include "DlgShadow.h"
#include "FrameComTitleBar.h"
#include <QVBoxLayout>

class CDlgComBase : public CDlgShadow
{
	Q_OBJECT

public:
	CDlgComBase(QWidget *parent = 0, bool bCenterDlg = true, bool bHasTitleBar = true);
	~CDlgComBase();

	void SetWindowsTitle(const QString& strTitle, bool bCheckPos = false);

	// 显示隐藏按钮
	void ShowMinBtn(bool bShow);
	void ShowMaxBtn(bool bShow);
	void ShowCloseBtn(bool bShow);
	void ShowSettingBtn(bool bShow);
	
	void ShowMaximized();
	
	void SetTitleBarObjectName(QString strObjectName);
	void SetHeadBarHeight(int nHeight);

protected:
	virtual bool IsCaption(int nXPos, int nYPos);
	QWidget* GetCenterWidget() { return &m_frameCenter; }
	virtual void OnNcLBtnDbClick(int nXPos, int nYPos);

protected slots:
	void OnTimerCenter();

private:
	CFrameComTitleBar		m_frameComTitleBar;
	QVBoxLayout				m_vBoxLayout;
	QFrame					m_frameCenter;
	bool					m_bHasTitleBar;
};

DlgComBase.cpp

cpp 复制代码
#include "DlgComBase.h"
#include <QTimer>

CDlgComBase::CDlgComBase(QWidget *parent, bool bCenterDlg, bool bHasTitleBar)
: CDlgShadow(parent), m_frameComTitleBar(this), m_frameCenter(this), m_bHasTitleBar(bHasTitleBar)
{
	m_frameComTitleBar.setObjectName("framComTitleBar");
	m_frameComTitleBar.setFixedHeight(GetHeadBarHeight());

	int nShadowLen = GetShadowLen();
	m_vBoxLayout.setContentsMargins(nShadowLen, nShadowLen, nShadowLen, nShadowLen);
	m_vBoxLayout.setSpacing(0);
	if (m_bHasTitleBar)
	{
		m_vBoxLayout.addWidget(&m_frameComTitleBar);
	}
	
	m_vBoxLayout.addWidget(&m_frameCenter);

	m_frameCenter.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
	setLayout(&m_vBoxLayout);

	if (bCenterDlg)
		QTimer::singleShot(10, this, SLOT(OnTimerCenter()));
}

CDlgComBase::~CDlgComBase()
{

}

void CDlgComBase::SetWindowsTitle(const QString& strTitle, bool bCheckPos)
{
	m_strTitle = strTitle;
	m_frameComTitleBar.SetWindowsTitle(strTitle, bCheckPos);
	setWindowTitle(strTitle);
}

void CDlgComBase::ShowMinBtn(bool bShow)
{
	m_frameComTitleBar.ShowMinBtn(bShow);
}

void CDlgComBase::ShowMaxBtn(bool bShow)
{
	SetHasMaxFun(bShow);
	m_frameComTitleBar.ShowMaxBtn(bShow);
}

void CDlgComBase::ShowCloseBtn(bool bShow)
{
	m_frameComTitleBar.ShowCloseBtn(bShow);
}

void CDlgComBase::ShowSettingBtn(bool bShow)
{
	m_frameComTitleBar.ShowSettingBtn(bShow);
}

bool CDlgComBase::IsCaption(int nXPos, int nYPos)
{
	QWidget* pChild = childAt(nXPos, nYPos);
	if (pChild == NULL)
	{
		ADD_LOGD("CDlgComBase::IsCaption() return true");
		return true;
	}

	if (pChild == &m_frameComTitleBar || pChild == m_frameComTitleBar.GetTitleLabel())
	{
		ADD_LOGD("CDlgComBase::IsCaption() return true");
		return true;
	}

	ADD_LOGD("CDlgComBase::IsCaption() return false");
	return false;
}

void CDlgComBase::SetTitleBarObjectName(QString strObjectName)
{
	m_frameComTitleBar.setObjectName(strObjectName);
}

void CDlgComBase::OnTimerCenter()
{
	CenterInParent((QWidget*)parent());
}

void CDlgComBase::SetHeadBarHeight(int nHeight)
{
	m_frameComTitleBar.setFixedHeight(nHeight);
	CDlgShadow::SetHeadBarHeight(nHeight);
}

void CDlgComBase::ShowMaximized()
{
	m_frameComTitleBar.ShowMaximized();
	CDlgShadow::ShowMaximized();
}

void CDlgComBase::OnNcLBtnDbClick(int nXPos, int nYPos)
{
	if (m_bHasMaxFun)
		m_frameComTitleBar.ShowMaxRestoreBtn(m_bMaximized);
	CDlgShadow::OnNcLBtnDbClick(nXPos, nYPos);
}

DlgShadow.h

cpp 复制代码
#ifndef SHADOWDLG_H
#define SHADOWDLG_H
#include <QDialog>
#include <QMouseEvent>

class CDlgShadow : public QDialog
{
	Q_OBJECT

public:
	CDlgShadow(QWidget *parent = 0);
	~CDlgShadow();

	void HideDlg();
	void ShowDlg();
	void SetDlgBkColor(QColor& clrDlgBk);
	void CenterInParent(QWidget* pWidget);
	void SetResizeable(bool bOn) { m_bResizeable = bOn; }
	virtual void OnBtnSettingClicked(QPoint& ptBtnBottom);
	virtual void OnBtnMinClicked();
	virtual void OnBtnMaxClicked();
	virtual void OnBtnRestoreClicked();
	virtual void OnBtnCloseClicked();
	virtual bool OnProHotKey(int nFsModifiers, int nVk);
	virtual void OnMsgEndSession();

	void ShowMaximized();

protected:
	void paintEvent(QPaintEvent* event);
	void keyPressEvent(QKeyEvent* event);
	int GetShadowLen() { return m_nShadowLen; }
	int GetHeadBarHeight() { return m_nHeadBarHeight; }
	void SetHeadBarHeight(int nHeight);
	void SetHasMaxFun(bool bHasMaxFun) { m_bHasMaxFun = bHasMaxFun; }
	bool nativeEvent(const QByteArray& eventType, void* pMessage, long* pResult);
	virtual bool IsCaption(int nXPos, int nYPos);
	virtual void OnNcLBtnDbClick(int nXPos, int nYPos);
	virtual void OnKeyReturnPress();
	virtual void OnKeyEscapePress();
	virtual void OnNcLBtnClick();
	void closeEvent(QCloseEvent *event);

protected:
	int					m_nFrameLen;		// 边框宽度,单位:像素
	int					m_nShadowLen;		// 阴影宽度,单位:像素
	int					m_nHeadBarHeight;	// 标题栏高度
	bool				m_bHasMaxFun;
	bool				m_bMaximized;
	bool				m_bNcLBtnClk;
	bool				m_bHideDlg;
	QString				m_strTitle;			// 调试时使用
	bool				m_bHotKey;			// 处理快捷键功能

private:
	QRect				m_rectDlg;
	QColor				m_clrDlgBk;
	bool				m_bResizeable;
};

#endif // SHADOWDLG_H

DlgShadow.cpp

cpp 复制代码
#include "DlgShadow.h"
#include <QPainter>
#include <qmath.h>
#include <QApplication>
#include <QDesktopWidget>
#include <Windows.h>

CDlgShadow::CDlgShadow(QWidget *parent)
	: QDialog(parent)
{
	setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog | Qt::WindowMinimizeButtonHint);
	setAttribute(Qt::WA_TranslucentBackground);
	
	m_nFrameLen = 10;
	m_nShadowLen = 6;
	m_nHeadBarHeight = 36;
	m_bMaximized = false;
	m_bHasMaxFun = true;
	m_clrDlgBk = QColor(255, 255, 255);
	m_bResizeable = true;
	m_bNcLBtnClk = false;
	m_bHideDlg = false;
	m_bHotKey = false;
}

CDlgShadow::~CDlgShadow()
{

}

void CDlgShadow::paintEvent(QPaintEvent* event)
{
	QPainterPath path;
	path.setFillRule(Qt::WindingFill);
	path.addRoundedRect(m_nShadowLen, m_nShadowLen, width() - 2 * m_nShadowLen, height() - 2 * m_nShadowLen, 2, 2);

	QPainter painter(this);
	painter.setRenderHint(QPainter::Antialiasing, true);
	painter.fillPath(path, QBrush(m_clrDlgBk));

	QColor color(0, 0, 0, 50);
	for (int i = 0; i < m_nShadowLen; i++)
	{
		QPainterPath pathShadow;
		pathShadow.setFillRule(Qt::WindingFill);
		pathShadow.addRoundedRect(m_nShadowLen - i, m_nShadowLen - i, width() - (m_nShadowLen - i) * 2, height() - (m_nShadowLen - i) * 2, 2 + i, 2 + i);
		int nAlpha = 50 - qSqrt(i) * 25;
		if (nAlpha < 0)
			nAlpha = 0;
		color.setAlpha(nAlpha);
		painter.setPen(color);
		painter.drawPath(pathShadow);
	}

	painter.setRenderHint(QPainter::Antialiasing, false);
	painter.fillPath(path, QBrush(m_clrDlgBk));
	QDialog::paintEvent(event);
}

void CDlgShadow::OnBtnMinClicked()
{
	showMinimized();
}

void CDlgShadow::OnBtnMaxClicked()
{
	m_bMaximized = true;
	m_rectDlg = geometry();
	setGeometry(-m_nShadowLen, -m_nShadowLen, QApplication::desktop()->availableGeometry().width() + m_nShadowLen * 2,
		QApplication::desktop()->availableGeometry().height() + m_nShadowLen * 2);
}

void CDlgShadow::OnBtnRestoreClicked()
{
	m_bMaximized = false;
	setFixedHeight(QWIDGETSIZE_MAX);
	setGeometry(m_rectDlg);
}

void CDlgShadow::SetDlgBkColor(QColor& clrDlgBk)
{
	m_clrDlgBk = clrDlgBk;
}

void CDlgShadow::SetHeadBarHeight(int nHeight)
{
	m_nHeadBarHeight = nHeight; 
}

bool CDlgShadow::IsCaption(int nXPos, int nYPos)
{
	if (childAt(nXPos, nYPos) == 0)
	{
		ADD_LOGD("CDlgShadow::IsCaption() return true");
		return true;
	}
	else
	{
		ADD_LOGD("CDlgShadow::IsCaption() return false");
		return false;
	}
}

bool CDlgShadow::nativeEvent(const QByteArray& eventType, void* pMessage, long* pResult)
{
	ADD_LOGD(QString("CDlgShadow::nativeEvent in"));

	if (m_bHideDlg)
	{
		ADD_LOGD(QString("CDlgShadow::nativeEvent out"));
		return QDialog::nativeEvent(eventType, pMessage, pResult);
	}

	const MSG* pMsg = static_cast<MSG*>(pMessage);
	if (pMsg->message == WM_NCHITTEST)
	{
		RECT rect;
		SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
		int nWin32Width = rect.right - rect.left;
		int nWin32Height = rect.bottom - rect.top;
		int nQtWidth = QApplication::desktop()->availableGeometry().width();
		int nQtHeight = QApplication::desktop()->availableGeometry().height();
		int nMsgX = ((int)(short)LOWORD(pMsg->lParam)) * nQtWidth / nWin32Width;
		int nMsgY = ((int)(short)HIWORD(pMsg->lParam)) * nQtHeight / nWin32Height;
		int xPos = nMsgX - frameGeometry().x();
		int yPos = nMsgY - frameGeometry().y();
		if (IsCaption(xPos, yPos))
		{
			*pResult = HTCAPTION;
		}
		else
		{
			ADD_LOGD(QString("CDlgShadow::nativeEvent out, WM_NCHITTEST pResult:%1").arg(*pResult));
			return false;
		}

		if (!m_bResizeable)
		{
			if (*pResult == HTCAPTION)
			{
				ADD_LOGD(QString("CDlgShadow::nativeEvent out, WM_NCHITTEST pResult:%1").arg(*pResult));
				return true;
			}

			ADD_LOGD(QString("CDlgShadow::nativeEvent out, WM_NCHITTEST pResult:%1").arg(*pResult));
			return QDialog::nativeEvent(eventType, pMessage, pResult);
		}

		if (xPos > 0 && xPos < m_nFrameLen)
			*pResult = HTLEFT;

		if (xPos >(width() - m_nFrameLen) && xPos < (width() - 0))
			*pResult = HTRIGHT;

		if (yPos > 0 && yPos < m_nFrameLen)
			*pResult = HTTOP;

		if (yPos >(height() - m_nFrameLen) && yPos < (height() - 0))
			*pResult = HTBOTTOM;

		if (xPos > 0 && xPos < m_nFrameLen && yPos > 0 && yPos < m_nFrameLen)
			*pResult = HTTOPLEFT;

		if (xPos >(width() - m_nFrameLen) && xPos < (width() - 0) && yPos > 0 && yPos < m_nFrameLen)
			*pResult = HTTOPRIGHT;

		if (xPos > 0 && xPos < m_nFrameLen && yPos >(height() - m_nFrameLen) && yPos < (height() - 0))
			*pResult = HTBOTTOMLEFT;

		if (xPos >(width() - m_nFrameLen) && xPos < (width() - 0) && yPos >(height() - m_nFrameLen) && yPos < (height() - 0))
			*pResult = HTBOTTOMRIGHT;

		ADD_LOGD(QString("CDlgShadow::nativeEvent out, WM_NCHITTEST pResult:%1").arg(*pResult));
		return true;
	}
	else if (pMsg->message == WM_NCLBUTTONDBLCLK)
	{
		int xPos = ((int)(short)LOWORD(pMsg->lParam)) - frameGeometry().x();
		int yPos = ((int)(short)HIWORD(pMsg->lParam)) - frameGeometry().y();
		OnNcLBtnDbClick(xPos, yPos);
		ADD_LOGD(QString("CDlgShadow::nativeEvent out, WM_NCLBUTTONDBLCLK"));
		return true;
	}
	else if (pMsg->message == WM_NCLBUTTONDOWN)
	{
		if (m_bNcLBtnClk)
		{
			OnNcLBtnClick();
		}
	}
	else if (pMsg->message == WM_HOTKEY)
	{
		if (m_bHotKey)
		{
			UINT nFuModifiers = (UINT)LOWORD(pMsg->lParam);  // 模式
			UINT nVirtKey = (UINT)HIWORD(pMsg->lParam);     // 键值
			if (OnProHotKey(nFuModifiers, nVirtKey))
			{
				ADD_LOGD(QString("CDlgShadow::nativeEvent out, WM_HOTKEY"));
				return true;
			}
		}
	}
	else if (pMsg->message == WM_ENDSESSION)
	{
		ADD_LOGD(QStringLiteral("截获关机指令1"));
		OnMsgEndSession();
	}

	ADD_LOGD(QString("CDlgShadow::nativeEvent out"));
	return QDialog::nativeEvent(eventType, pMessage, pResult);
}

void CDlgShadow::OnNcLBtnDbClick(int nXPos, int nYPos)
{
	if (!m_bHasMaxFun)
		return;

	if (nYPos > m_nFrameLen + m_nHeadBarHeight)
		return;

	if (m_bMaximized)
	{
		OnBtnRestoreClicked();
	}
	else
	{
		OnBtnMaxClicked();
	}
}

void CDlgShadow::CenterInParent(QWidget* pWidget)
{
	int nXPos = 0;
	int nYPos = 0;
	if (pWidget == NULL)
	{
		nXPos = (QApplication::desktop()->width() - width()) / 2;
		nYPos = (QApplication::desktop()->height() - height()) / 2;
	}
	else
	{
		QWidget* pParent = (QWidget*)pWidget->parent();
// 		if (pParent != NULL)
// 		{
// 			//QPoint ptGloba = pWidget->mapToGlobal(QPoint(0, 0));
// 			nXPos = /*ptGloba.x() + */(pWidget->width() - width()) / 2;
// 			nYPos = /*ptGloba.y() + */(pWidget->height() - height()) / 2;
// 		}
// 		else
		{
			QPoint ptGloba = pWidget->mapToGlobal(QPoint(0, 0));
			nXPos = ptGloba.x() + (pWidget->width() - width()) / 2;
			nYPos = ptGloba.y() + (pWidget->height() - height()) / 2;
		}
	}
	move(nXPos, nYPos);
}

void CDlgShadow::keyPressEvent(QKeyEvent* event)
{
	if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return/* || event->key() == Qt::Key_Space*/)
	{
		OnKeyReturnPress();
		event->accept();
	}
	else if (event->key() == Qt::Key_Escape)
	{
		OnKeyEscapePress();
		event->ignore();
	}
}


void CDlgShadow::OnKeyReturnPress()
{
	//accept();
}

void CDlgShadow::OnKeyEscapePress()
{
	//reject();
}

void CDlgShadow::OnBtnCloseClicked()
{
	reject();
}

void CDlgShadow::OnBtnSettingClicked(QPoint& ptBtnBottom)
{

}

void CDlgShadow::OnNcLBtnClick()
{

}

void CDlgShadow::HideDlg()
{
	m_bHideDlg = true;
	setWindowOpacity(0);
}

void CDlgShadow::ShowDlg()
{
	setWindowOpacity(1);
	m_bHideDlg = false;
}

void CDlgShadow::closeEvent(QCloseEvent *event)
{
	event->ignore();
	OnBtnCloseClicked();
}

bool CDlgShadow::OnProHotKey(int nFsModifiers, int nVk)
{
	return false;
}

void CDlgShadow::OnMsgEndSession()
{

}

void CDlgShadow::ShowMaximized()
{
	m_bMaximized = true;
	int nXPos = (QApplication::desktop()->availableGeometry().width() - (1273 + 11)) / 2;
	int nYPos = (QApplication::desktop()->availableGeometry().height() - (878 + 11)) / 2;
	int nMaxHeight = QApplication::desktop()->availableGeometry().height() + m_nShadowLen * 2;
	//setFixedHeight(nMaxHeight);
	setFixedHeight(QWIDGETSIZE_MAX);

	m_rectDlg = QRect(nXPos, nYPos, (1273 + 11), (878 + 11));
	setGeometry(-m_nShadowLen, -m_nShadowLen, QApplication::desktop()->availableGeometry().width() + m_nShadowLen * 2,
		QApplication::desktop()->availableGeometry().height() + m_nShadowLen * 2);
}
相关推荐
陌小呆^O^4 分钟前
Cmakelist.txt之win-c-udp-server
c语言·开发语言·udp
C++忠实粉丝5 分钟前
计算机网络socket编程(3)_UDP网络编程实现简单聊天室
linux·网络·c++·网络协议·计算机网络·udp
Gu Gu Study11 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Mr.Q14 分钟前
OpenCV和Qt坐标系不一致问题
qt·opencv
时光の尘26 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
我们的五年30 分钟前
【Linux课程学习】:进程描述---PCB(Process Control Block)
linux·运维·c++
以后不吃煲仔饭40 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师40 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者44 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
程序猿阿伟1 小时前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链