Qt::QWidget 无默认标题栏边框的拖拽修改大小方式
开发环境:win10+vs2015+qt5.9.1
背景:开发过程中,一般很少会使用系统提供的标题栏和边框;往往都是自定义一个自己设计的方案。这时候在QWidget中需要加上flag:Qt::FramelessWindowHint(实现方式很容易百度就不再赘述)。但是这样带来的问题就是系统自带的标题栏边框提供的拖拽移动和拖拽修改窗口大小的功能被废弃掉。这样就需要自己实现一个方案来提供这个功能。
实现:拖拽移动在很多文章中都应该有介绍了,代码也非常简单,不再赘述,主要讲一下拖拽修改窗口大小的方式;方案有两个,一个是借用系统平台自己的处理逻辑来完成,这里贴上windows平台的方案(Linux的没有尝试但应该也是有对应方案适应的)
/* * Description: 无边框可拖拽大小窗口 * Author: 公子开明 KaiMing Prince * Detail: 边框的拖拽大小是windows默认实现的效果,为了美化窗口经常需要去掉默认边框自定义标题栏和边框等部分,这样拖拽效果需要自己实现 * Class: FrameLessDragResizeWidget * Implement: 本类实现了无边框的QWidget拖拽大小效果 */ #ifndef _FRAMELESS_DRAG_RESIZE_WIDGET_H__ #define _FRAMELESS_DRAG_RESIZE_WIDGET_H__ #include <QWidget> class FrameLessDragResizeWidget : public QWidget { public: FrameLessDragResizeWidget(QWidget *parent=Q_NULLPTR); ~FrameLessDragResizeWidget(); protected: //第一种方案是走windows自己的消息机制 virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result); }; #endif //_FRAMELESS_DRAG_RESIZE_WIDGET_H__
可以看到qt自己提供了一个nativeEvent事件负责处理系统事件,在windows平台则是窗口的消息机制,所以我们只要在我们认为适合的地方回馈给系统本身的拖拽修改消息就可以实现
#include "FrameLessDragResizeWidget.h"
#include <QMouseEvent> #include <qt_windows.h> const int g_nBorder = 4; FrameLessDragResizeWidget::FrameLessDragResizeWidget(QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) { setMouseTracking(true); } FrameLessDragResizeWidget::~FrameLessDragResizeWidget() { } bool FrameLessDragResizeWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) { MSG* pMsg = (MSG*)message; switch (pMsg->message) { case WM_NCHITTEST: { QPoint pos = mapFromGlobal(QPoint(LOWORD(pMsg->lParam), HIWORD(pMsg->lParam))); bool bHorLeft = pos.x() < g_nBorder; bool bHorRight = pos.x() > width() - g_nBorder; bool bVertTop = pos.y() < g_nBorder; bool bVertBottom = pos.y() > height() - g_nBorder; if (bHorLeft && bVertTop) { *result = HTTOPLEFT; } else if (bHorLeft && bVertBottom) { *result = HTBOTTOMLEFT; } else if (bHorRight && bVertTop) { *result = HTTOPRIGHT; } else if (bHorRight && bVertBottom) { *result = HTBOTTOMRIGHT; } else if (bHorLeft) { *result = HTLEFT; } else if (bHorRight) { *result = HTRIGHT; } else if (bVertTop) { *result = HTTOP; } else if (bVertBottom) { *result = HTBOTTOM; } else { return false; } return true; } break; default: break; } return QWidget::nativeEvent(eventType, message, result); }
自定义了一个边框宽度为4,在这个范围内视为可拖拽区域;当然也可以自定义其它数值;主要处理看nativeEvent内部处理,在适当的判断下返回HTXX这种事件给系统,让系统来处理修改窗口大小的工作
方案2:方案1的问题在于,只适用于windows平台;如果修改平台就需要再一份代码处理。所以还是应该考虑用qt本身的处理方式来解决,避免多余实现;既然是qt实现,就要考虑鼠标的各个事件处理了,比如移动到边缘的光标状态改变,在可拖拽的范围内按下拖拽的移动等。。具体贴代码再分析(代码里还是保留了方案1,不需要的可以自己剔除)
/* * Description: 无边框可拖拽大小窗口 * Author: 公子开明 KaiMing Prince * Detail: 边框的拖拽大小是windows默认实现的效果,为了美化窗口经常需要去掉默认边框自定义标题栏和边框等部分,这样拖拽效果需要自己实现 * Class: FrameLessDragResizeWidget * Implement: 本类实现了无边框的QWidget拖拽大小效果 */ #ifndef _FRAMELESS_DRAG_RESIZE_WIDGET_H__ #define _FRAMELESS_DRAG_RESIZE_WIDGET_H__ #include <QWidget> enum DRAG_ABSOLUTE_POSITION { DRAG_KEEP=0, //保持不动 DRAG_LOCK_LEFT, //锁定左边坐标 DRAG_LOCK_TOP=1, //锁定上边坐标 DRAG_LOCK_RIGHT, //锁定右边坐标 DRAG_LOCK_BOTTOM=2 //锁定下边坐标 }; class FrameLessDragResizeWidget : public QWidget { public: FrameLessDragResizeWidget(QWidget *parent=Q_NULLPTR); ~FrameLessDragResizeWidget(); protected: //第一种方案是走windows自己的消息机制 virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result); //第二种方案是通过鼠标事件自己判断处理 virtual void mousePressEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); private: bool m_bCanDragResize; //当前窗口是否处于可拖拽窗口大小状态 QPoint m_start_drag_point; //拖拽开始的起始位置 DRAG_ABSOLUTE_POSITION m_emHorizontal; //横向的移动范围 DRAG_ABSOLUTE_POSITION m_emVertical; //纵向的移动范围 }; #endif //_FRAMELESS_DRAG_RESIZE_WIDGET_H__
枚举DRAG_ABSOLUTE_POSITION表示拖拽的一种状态,左右和上下的拖拽情况,肯定有一个保持不动的状态,我们可以通过这个状态来确认窗口的改变是怎样的。比如,我们在左上角拖拽的时候,右下角这个点肯定是保持原来的位置不变,而修改的是左上角的位置。这样在拖拽的时候就能判断geometry如何修改
#include "FrameLessDragResizeWidget.h" #include <QMouseEvent> //#define _NATIVE_EVENT_ #ifdef _NATIVE_EVENT_ #include <qt_windows.h> #endif const int g_nBorder = 4; FrameLessDragResizeWidget::FrameLessDragResizeWidget(QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint), m_bCanDragResize(false) { setMouseTracking(true); } FrameLessDragResizeWidget::~FrameLessDragResizeWidget() { } bool FrameLessDragResizeWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) { #ifdef _NATIVE_EVENT_ MSG* pMsg = (MSG*)message; switch (pMsg->message) { case WM_NCHITTEST: { QPoint pos = mapFromGlobal(QPoint(LOWORD(pMsg->lParam), HIWORD(pMsg->lParam))); bool bHorLeft = pos.x() < g_nBorder; bool bHorRight = pos.x() > width() - g_nBorder; bool bVertTop = pos.y() < g_nBorder; bool bVertBottom = pos.y() > height() - g_nBorder; if (bHorLeft && bVertTop) { *result = HTTOPLEFT; } else if (bHorLeft && bVertBottom) { *result = HTBOTTOMLEFT; } else if (bHorRight && bVertTop) { *result = HTTOPRIGHT; } else if (bHorRight && bVertBottom) { *result = HTBOTTOMRIGHT; } else if (bHorLeft) { *result = HTLEFT; } else if (bHorRight) { *result = HTRIGHT; } else if (bVertTop) { *result = HTTOP; } else if (bVertBottom) { *result = HTBOTTOM; } else { return false; } return true; } break; default: break; } #endif return QWidget::nativeEvent(eventType, message, result); } void FrameLessDragResizeWidget::mousePressEvent(QMouseEvent *event) { #ifndef _NATIVE_EVENT_ if (Qt::LeftButton == event->button() && m_bCanDragResize) { m_start_drag_point = event->globalPos(); } #endif QWidget::mousePressEvent(event); } void FrameLessDragResizeWidget::mouseMoveEvent(QMouseEvent *event) { #ifndef _NATIVE_EVENT_ if (Qt::LeftButton & event->buttons()) { if (m_bCanDragResize) //如果左键按住移动且在拖拽状态 { const QPoint point_offset = event->globalPos() - m_start_drag_point; m_start_drag_point = event->globalPos(); int nOffsetX1 = DRAG_LOCK_RIGHT == m_emHorizontal ? point_offset.x() : 0; int nOffsetY1 = DRAG_LOCK_BOTTOM == m_emVertical ? point_offset.y() : 0; int nOffsetX2 = DRAG_LOCK_LEFT == m_emHorizontal ? point_offset.x() : 0; int nOffsetY2 = DRAG_LOCK_TOP == m_emVertical ? point_offset.y() : 0; setGeometry(geometry().adjusted(nOffsetX1, nOffsetY1, nOffsetX2, nOffsetY2)); } } else if (Qt::NoButton == event->button()) { //先判断是否光标在可拖拽大小的窗口位置 const QPoint& pos = event->pos(); bool bHorLeft = pos.x() < g_nBorder; bool bHorRight = pos.x() > rect().right() - g_nBorder; bool bVertTop = pos.y() < g_nBorder; bool bVertBottom = pos.y() > rect().bottom() - g_nBorder; if (bHorLeft || bHorRight || bVertTop || bVertBottom) { m_bCanDragResize = true; if (bHorLeft && bVertTop) //左上角拖拽 { setCursor(Qt::SizeFDiagCursor); m_emHorizontal = DRAG_LOCK_RIGHT; m_emVertical = DRAG_LOCK_BOTTOM; } else if (bHorLeft && bVertBottom) //左下角拖拽 { setCursor(Qt::SizeBDiagCursor); m_emHorizontal = DRAG_LOCK_RIGHT; m_emVertical = DRAG_LOCK_TOP; } else if (bHorRight && bVertTop) //右上角拖拽 { setCursor(Qt::SizeBDiagCursor); m_emHorizontal = DRAG_LOCK_LEFT; m_emVertical = DRAG_LOCK_BOTTOM; } else if (bHorRight && bVertBottom) //右下角拖拽 { setCursor(Qt::SizeFDiagCursor); m_emHorizontal = DRAG_LOCK_LEFT; m_emVertical = DRAG_LOCK_TOP; } else if (bHorLeft) //左边框拖拽 { setCursor(Qt::SizeHorCursor); m_emHorizontal = DRAG_LOCK_RIGHT; m_emVertical = DRAG_KEEP; } else if (bHorRight) //右边框拖拽 { setCursor(Qt::SizeHorCursor); m_emHorizontal = DRAG_LOCK_LEFT; m_emVertical = DRAG_KEEP; } else if (bVertTop) //上边框拖拽 { setCursor(Qt::SizeVerCursor); m_emHorizontal = DRAG_KEEP; m_emVertical = DRAG_LOCK_BOTTOM; } else if (bVertBottom) //下边框拖拽 { setCursor(Qt::SizeVerCursor); m_emHorizontal = DRAG_KEEP; m_emVertical = DRAG_LOCK_TOP; } } else if (m_bCanDragResize) { m_bCanDragResize = false; setCursor(Qt::ArrowCursor); //如果上一次判断修改了光标,不再是拖拽的状态把光标改回来 } } #endif QWidget::mouseMoveEvent(event); } void FrameLessDragResizeWidget::mouseReleaseEvent(QMouseEvent *event) { QWidget::mouseReleaseEvent(event); }
首先看mousemove的nobutton情况,在边缘范围修改光标状态并记录边缘位置状态;然后根据是否拖拽的标识判断执行窗口大小的修改,根据鼠标位置偏移来确定改变大小数值,状态来确认adjusted的参数填充,并不断更新起始坐标位置。
效果图就不录制上传了,各位有兴趣可以直接copy代码尝试;下一篇谈谈拖拽移动和拖拽大小的合并处理。