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 行搞定

  • 边缘拖拽流畅

  • 支持四个边 + 四个角

缺点

  • 部分系统样式会轻微冲突

  • 自定义标题栏需要自己写拖动

手动检测鼠标位置 + 边缘缩放

原理

判断鼠标是否靠近窗口边缘,改变光标形状,然后拖动缩放。

实现步骤

  1. 定义边缘宽度

  2. 重写 mouseMoveEvent 判断位置

  3. 重写 mousePressEvent / mouseReleaseEvent 开始 / 结束缩放

  4. 重写 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 开源)

使用方法

  1. 下载集成

  2. 直接继承 FramelessWindow

  3. 自带:缩放 + 拖动 + 阴影 + 动画

代码示例

#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;
}