開發環境: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代碼嘗試;下一篇談談拖拽移動和拖拽大小的合並處理。