QT无边框窗口的移动以及缩放
QT无边框窗口拖动
全局拖动(点窗口任意位置都能拖)
#include <QWidget>
#include <QMouseEvent>
class DragWidget : public QWidget
{
Q_OBJECT
public:
explicit DragWidget(QWidget *parent = nullptr);
protected:
// 重写鼠标事件
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
bool m_isDrag; // 是否正在拖动
QPoint m_dragPos; // 鼠标相对窗口左上角的偏移
};优点
代码最少,1 分钟写完
点窗口任意地方都能拖
缺点
点哪里都能拖,不适合正式软件(一般只允许拖标题栏)
仅自定义标题栏拖动(推荐!)
void DragWidget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
// ✅ 关键:只允许在 标题栏区域(0,0,width,40) 内拖动
if(event->pos().y() <= 40)
{
m_isDrag = true;
m_dragPos = event->globalPos() - this->geometry().topLeft();
event->accept();
}
}
}单独标题栏控件拖动(解耦写法)
// 在你的 TitleBar 类里
void TitleBar::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
// 告诉父窗口(主窗口)开始拖动
this->window()->setProperty("dragPos", event->globalPos() - this->window()->pos());
}
}
void TitleBar::mouseMoveEvent(QMouseEvent *event)
{
QPoint dragPos = this->window()->property("dragPos").toPoint();
if(!dragPos.isNull())
{
this->window()->move(event->globalPos() - dragPos);
}
}WinAPI方式
#pragma once
#include <QLabel>
#include <QPushButton>
#include <QWidget>
class MyTitleBarWidget : public QWidget {
Q_OBJECT
public:
MyTitleBarWidget(QWidget* parent);
~MyTitleBarWidget();
private:
void mousePressEvent(QMouseEvent* event) override;
};
#include "MyTitleBarWidget.h"
#include <qt_windows.h>
#include <QHBoxLayout>
#include <QMouseEvent>
MyTitleBarWidget::MyTitleBarWidget(QWidget* parent) : QWidget(parent) {
// 设置为无边框窗口
this->setWindowFlags(Qt::WindowType::FramelessWindowHint |
Qt::WindowType::WindowMinMaxButtonsHint);
// 禁止父窗口影响子窗口样式
this->setAttribute(Qt::WA_StyledBackground);
}
MyTitleBarWidget::~MyTitleBarWidget() {}
}
void MyTitleBarWidget::mousePressEvent(QMouseEvent* event) {
if (ReleaseCapture()) {
QWidget* windows = this->window();
if (windows->isTopLevel()) {
SendMessage(HWND(windows->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION,
0);
}
}
event->ignore();
}QT无边框窗口缩放
使用Qt自带的原生缩放
原理
给窗口加一个属性,让 Qt 自动处理边缘缩放,不用写任何鼠标事件。
代码
this->setWindowFlags(Qt::FramelessWindowHint);
// 关键:开启原生窗口缩放
this->setAttribute(Qt::WA_NativeWindow);
this->setWindowFlags(this->windowFlags() | Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint);优点
超级简单,1 行搞定
边缘拖拽流畅
支持四个边 + 四个角
缺点
部分系统样式会轻微冲突
自定义标题栏需要自己写拖动
手动检测鼠标位置 + 边缘缩放
原理
判断鼠标是否靠近窗口边缘,改变光标形状,然后拖动缩放。
实现步骤
定义边缘宽度
重写
mouseMoveEvent判断位置重写
mousePressEvent/mouseReleaseEvent开始 / 结束缩放重写
mouseReleaseEvent结束缩放
完整代码
#include <QWidget>
#include <QMouseEvent>
class FramelessWidget : public QWidget
{
Q_OBJECT
public:
explicit FramelessWidget(QWidget *parent = nullptr);
protected:
// 重写鼠标事件
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
// 判断鼠标在哪个边缘/角落
Qt::Edges getEdge(const QPoint &pos);
bool m_isResize; // 是否正在缩放
Qt::Edges m_resizeEdge; // 缩放的边
int m_edgeWidth; // 边缘宽度(可缩放区域)
};FramelessWidget::FramelessWidget(QWidget *parent) : QWidget(parent)
{
// 无边框
this->setWindowFlags(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_Hover);
m_isResize = false;
m_edgeWidth = 8; // 边缘8像素都可以缩放
}
// 判断鼠标位置:上下左右 + 四个角
Qt::Edges FramelessWidget::getEdge(const QPoint &pos)
{
int w = this->width();
int h = this->height();
bool left = pos.x() < m_edgeWidth;
bool right = pos.x() > w - m_edgeWidth;
bool top = pos.y() < m_edgeWidth;
bool bottom = pos.y() > h - m_edgeWidth;
if (left && top) return Qt::TopEdge | Qt::LeftEdge;
if (left && bottom) return Qt::BottomEdge | Qt::LeftEdge;
if (right && top) return Qt::TopEdge | Qt::RightEdge;
if (right && bottom) return Qt::BottomEdge | Qt::RightEdge;
if (left) return Qt::LeftEdge;
if (right) return Qt::RightEdge;
if (top) return Qt::TopEdge;
if (bottom) return Qt::BottomEdge;
return Qt::Edges();
}
void FramelessWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_resizeEdge = getEdge(event->pos());
if (m_resizeEdge != Qt::Edges()) {
m_isResize = true;
event->accept();
}
}
}
void FramelessWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!m_isResize) {
// 设置鼠标样式
Qt::Edges edge = getEdge(event->pos());
if (edge == (Qt::TopEdge | Qt::LeftEdge) || edge == (Qt::BottomEdge | Qt::RightEdge))
this->setCursor(Qt::SizeFDiagCursor);
else if (edge == (Qt::TopEdge | Qt::RightEdge) || edge == (Qt::BottomEdge | Qt::LeftEdge))
this->setCursor(Qt::SizeBDiagCursor);
else if (edge == Qt::LeftEdge || edge == Qt::RightEdge)
this->setCursor(Qt::SizeHorCursor);
else if (edge == Qt::TopEdge || edge == Qt::BottomEdge)
this->setCursor(Qt::SizeVerCursor);
else
this->setCursor(Qt::ArrowCursor);
return;
}
// 执行缩放
QRect rect = this->geometry();
QPoint globalPos = event->globalPos();
if (m_resizeEdge & Qt::LeftEdge) rect.setLeft(globalPos.x());
if (m_resizeEdge & Qt::RightEdge) rect.setRight(globalPos.x());
if (m_resizeEdge & Qt::TopEdge) rect.setTop(globalPos.y());
if (m_resizeEdge & Qt::BottomEdge)rect.setBottom(globalPos.y());
this->setGeometry(rect);
}
void FramelessWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_isResize = false;
m_resizeEdge = Qt::Edges();
}
}优点
兼容性最好
全平台通用
可自定义缩放区域大小
配合自定义标题栏完美
使用 QWidget::windowHandle ()->setSystemResizeMargins ()
仅支持 Qt 5.15+ / Qt6
代码
this->setWindowFlags(Qt::FramelessWindowHint);
// 自动开启边缘缩放,上下左右各8像素
this->windowHandle()->setSystemResizeMargins(QMargins(8,8,8,8));优点
代码最短
系统原生缩放,流畅
缺点
低版本 Qt 不能用
使用第三方无边框库(推荐正式项目)
推荐:Qt-Frameless-Window(GitHub 开源)
使用方法
下载集成
直接继承
FramelessWindow自带:缩放 + 拖动 + 阴影 + 动画
代码示例
#include "framelesswindow.h"
FramelessWindow window;
window.setContent(widget); // 你的界面
window.show();
优点
专业级无边框窗口
支持 Windows /macOS/ Linux
自带阴影、动画、最小化、最大化、关闭
重载nativeEvent
#pragma once
#include <QtWidgets/QWidget>
#include "MyTitleBarWidget.h"
class CustomTitleBar : public QWidget {
Q_OBJECT
public:
CustomTitleBar(QWidget* parent = nullptr);
~CustomTitleBar();
private:
bool nativeEvent(const QByteArray& eventType, void* message,
long* result) override;
private:
MyTitleBarWidget* titleBar = nullptr;
private:
int m_nBorderWidth = 5;
};
#include "CustomTitleBar.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>
#include <QVBoxLayout>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "dwmapi.lib")
CustomTitleBar::CustomTitleBar(QWidget* parent) : QWidget(parent) {
// 设置为无边框窗口
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
// 开启窗口鼠标悬停检测
this->setAttribute(Qt::WA_Hover);
}
CustomTitleBar::~CustomTitleBar() {}
bool CustomTitleBar::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 (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;
}
Comments