qt 拖拽 修改大小(二)
最近项目需要实现windows下橡皮筋的效果,所以对此做了一些了解,特此记录。
首先windows系统是支持橡皮筋效果的,需要使用win32方 法:SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, showFullWindow, NULL, 0);showFullWindow是一个变量,如果需要windows默认支持橡皮筋则需要传递参数false,否则传递参数true,如果使用 windows默认的橡皮筋缩放,效果如图1所示,会产生一个矩形框,不管是窗口移动还是放大缩小,都会对该矩形框作用,然后当鼠标弹起时,真实窗口才会 移动或者放大缩小。如果不使用橡皮筋拖拽的方式,那么窗口就是实时的拖拽。
图1 windows橡皮筋
在使用Qt窗口时,如果需要支持windows系统这种方式的拖拽,不能够使用setGeometry该函数来移动或者放大缩小窗口,而需要 重写QWidget::nativeEvent这个方法,该方法是在消息进入qt事件循环之前调用的,也就是说该方法会在mouseEvent等方法之前 调用,nativeEvent方法的实现请看另一篇文章qt 拖拽 修改大小,不过在我使用的过程中,使用了HTCAPTION这个属性后,原始窗口的双击放大事件被屏蔽掉了,到现在原因未搞清。在qt 拖拽 修改大小这篇文字中提到的bug,我用迂回的方式解决了,那就是使用Qt::WindowSystemMenuHint属性,但是窗口的放大和缩小使用另一种方式解决。
下面就是我使用代理的方式来支持窗口拖拽,由于该代理代码行数过多,我只写下重点的部分,该代理代码我也是从别人那儿拷贝的,后来根据我自己的理解和项目需求添加了一些东西。
代理头文件
#ifndef NC_FRAMELESS_HELPER_H #define NC_FRAMELESS_HELPER_H #include #include #include #include "commonControls/include/commoncontrols_global.h" class WidgetResizeHandlerImpl; class CRubberBand : public QRubberBand { public: CRubberBand(QRubberBand::Shape s, QWidget * parent = nullptr); ~CRubberBand(); protected: virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; void changeEvent(QEvent *) Q_DECL_OVERRIDE; void showEvent(QShowEvent *) Q_DECL_OVERRIDE; void moveEvent(QMoveEvent *) Q_DECL_OVERRIDE; private: }; //鼠标状态,可以获取鼠标当前和目标窗口的关系 class CursorPosCalculator { public: CursorPosCalculator(){ reset(); } void reset(); void recalculate(const QPoint& globalMousePos, const QRect& frameRect); public: bool onEdges; bool onLeftEdge; bool onRightEdge; bool onTopEdge; bool onBottomEdge; bool onTopLeftEdge; bool onBottomLeftEdge; bool onTopRightEdge; bool onBottomRightEdge; static int mBorderWidth; }; //真正的处理操作类 class WidgetData { public: WidgetData(WidgetResizeHandlerImpl * d, QWidget* topLevelWidget); ~WidgetData(); QWidget * widget(); void handleWidgetEvent(QEvent * event);//处理指定窗口事件入口函数 void updateRubberBandStatus(); private: void updateCursorShape(const QPoint& globalMousePos); void resizeWidget(const QPoint& globalMousePos); void moveWidget(const QPoint& globalMousePos); void handleMousePressEvent(QMouseEvent* event); void handleMouseReleaseEvent(QMouseEvent* event); void handleMouseMoveEvent(QMouseEvent* event); void handleLeaveEvent(QEvent* event); void handleHoverMoveEvent(QHoverEvent* event); private: bool mLeftButtonPressed = false; bool mCursorShapeChanged = false; Qt::WindowFlags mWindowFlags; QPoint mDragPos;//拖拽位置起点 QWidget * mWidget = nullptr;//被代理的窗口指针 CRubberBand * mRubberBand = nullptr;//橡胶类,支持橡胶操作 CursorPosCalculator mPressedMousePos;//鼠标按下时光标信息 CursorPosCalculator mMoveMousePos;//鼠标移动时光标信息 WidgetResizeHandlerImpl * d_ptr; }; ///说明:当QWidget设置了Qt::FramelessWindowHint属性时,可以借助该类完成:拖拽+窗口大小更改 class COMMONCONTROLS_EXPORT WidgetResizeHandler : public QObject { public: explicit WidgetResizeHandler(QObject* parent = ); ~WidgetResizeHandler(); public: void activateOn(QWidget * topLevelWidget);//添加topLevelWidget事件代理 void removeFrom(QWidget * topLevelWidget);//移除topLevelWidget事件代理 Qt::CursorShape CursorShape(QWidget * widget); //窗口移动 default:true void setWidgetMovable(bool movable); bool isWidgetMovable(); //大小可变 default:true void setWidgetResizable(bool resizable); bool isWidgetResizable(); // 橡胶式窗口移动 default:false void useRubberBandOnMove(bool use); bool isUsingRubberBandOnMove(); //橡胶式修改大小 default:false void useRubberBandOnResize(bool use); bool isUsingRubberBandOnResisze(); void setBorderWidth(int newBorderWidth); int borderWidth(); //局部可移动 void useLocalMoveabled(bool use); void addLocalWidget(QWidget *); protected: virtual bool eventFilter(QObject * obj, QEvent * event) Q_DECL_OVERRIDE;//????????????????????????????????????? private: WidgetResizeHandlerImpl * d_ptr; }; #endif // NC_FRAMELESS_HELPER_H
上边头文件中都有基本的注释,我就不过多解释了,下边我主要说下原理
在需要代理的类中声明WidgetResizeHandler对象,然后使用activateOn方法把需要代理的窗口添加到代理,注意被代 理的窗口需要含有Qt::Window属性明也就是需要时顶层窗口,如果对于一个复杂的窗口进行代理时,可能会出现一些意向不到的问题,比如:1、 QLabel接受富文本时,代理拿不到鼠标弹起事件,QToolButton对象不放到布局时,代理也拿不到鼠标弹起事件,这会导致代理不能正常使用,因 此我对该代理进行了修改,添加了useLocalMoveabled接口,允许代理只对局部窗口进行移动,这样是解决了我前边提到的两个问题。如果仔细看 应该也能看到我的代理也是使用setGeometry方法来拖拽窗口的,那么和我之前谈论的橡皮筋方式就有出入了,这个时候重点才来了,哈哈哈,继续往下 看,头文件中有个类CRubberBand,他是继承自QRubberBand,该类就模拟了一个橡皮筋的过程,只是qt提供的类接口有限,有一些写过很 难达到,比如说我要实现一些复杂的代理界面,那么我们就只能自己绘制了,我通过重新实现paintEvent函数,对该类画了一个灰色的矩形框,代码如 下:
QPainter p(this);
p.setPen(QPen(QColor(102, 102, 102), 4));
QRect rect = this->rect().adjusted(2, 2, -3, -3);
p.drawRect(rect);
如果照着我我上边所说的流程走,就会发现除了一个矩形框之外还会有一个背景色填充,这个时候就奇怪了,我们paintEvent并没有画背景 啊,呵呵呵,只需要在构造函数里加上这句话即可setAttribute(Qt::WA_NoSystemBackground),效果如图2所示。
图2 定制橡皮筋
下边我添加一些代理部分代码
1、CRubberBand构造函数
CRubberBand::CRubberBand(QRubberBand::Shape s, QWidget * parent) :QRubberBand(QRubberBand::Rectangle, parent) { setAttribute(Qt::WA_TranslucentBackground); #ifndef Q_DEAD_CODE_FROM_QT4_WIN setAttribute(Qt::WA_NoSystemBackground); #endif //Q_DEAD_CODE_FROM_QT4_WIN setWindowFlags(windowFlags() | Qt::FramelessWindowHint); }
2、使用activateOn后,窗口存储并验证
WidgetData::WidgetData(WidgetResizeHandlerImpl * d, QWidget * topLevelWidget) { this->d_ptr = d; mWidget = topLevelWidget; mWidget->setMouseTracking(true); mWindowFlags = mWidget->windowFlags(); //mWindowFlags |= Qt::CustomizeWindowHint | Qt::FramelessWindowHint; mWindowFlags |= Qt::FramelessWindowHint; mWidget->setWindowFlags(mWindowFlags); //mWidget->setWindowFlags( Qt::Popup | Qt::CustomizeWindowHint|Qt::FramelessWindowHint ); //Bug fix, mouse move events does not propagate from child widgets. //so need the hover events. mWidget->setAttribute(Qt::WA_Hover); updateRubberBandStatus(); bool visible = mWidget->isVisible();//防止非widget被代理 mWidget->setVisible(visible); }
3、当被代理的窗口有鼠标事件时,先有代理处理
void WidgetData::handleWidgetEvent(QEvent * event) { switch (event->type()) { case QEvent::MouseButtonPress: handleMousePressEvent(static_cast(event)); break; case QEvent::MouseButtonRelease: handleMouseReleaseEvent(static_cast(event)); break; case QEvent::MouseMove: handleMouseMoveEvent(static_cast(event)); break; case QEvent::Leave: handleLeaveEvent(event); break; case QEvent::HoverMove: handleHoverMoveEvent(static_cast(event)); break; } }
4、下边5个函数分别是上边的具体实现
void WidgetData::handleMousePressEvent(QMouseEvent * event) { if (event->button() == Qt::LeftButton) { mLeftButtonPressed = true; QRect frameRect = mWidget->frameGeometry(); mPressedMousePos.recalculate(event->globalPos(), frameRect); mDragPos = event->globalPos() - frameRect.topLeft(); if (mPressedMousePos.onEdges) { if (d_ptr->mUseRubberBandOnResize) { mRubberBand->setGeometry(frameRect); //mRubberBand->show(); } } else if (d_ptr->mUseRubberBandOnMove) { mRubberBand->setGeometry(frameRect); //mRubberBand->show(); } if (d_ptr->mLocalOnMove)//启用局部拖拽功能后 需要处理不在指定范围内的拖拽,并过滤掉 { bool canMove = false; for (int i = ; i < d_ptr->mLocalWidget.size(); ++i) { if (d_ptr->mLocalWidget[i]->rect().contains(d_ptr->mLocalWidget[i]->mapFromGlobal(event->globalPos()))) { canMove = true; break; } } if (canMove == false && mPressedMousePos.onEdges == false) { mLeftButtonPressed = false; } } } } void WidgetData::handleMouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { d_ptr->mCanMoveFlag = false; mLeftButtonPressed = false; mPressedMousePos.reset(); if (mRubberBand && mRubberBand->isVisible()) { mRubberBand->hide(); mWidget->setGeometry(mRubberBand->geometry()); } } } void WidgetData::handleMouseMoveEvent(QMouseEvent* event) { if (mLeftButtonPressed) { if (d_ptr->mWidgetResizable && mPressedMousePos.onEdges) { resizeWidget(event->globalPos()); } else if (d_ptr->mWidgetMovable) { moveWidget(event->globalPos()); } } else if (d_ptr->mWidgetResizable) { updateCursorShape(event->globalPos()); } } void WidgetData::handleLeaveEvent(QEvent*) { if (!mLeftButtonPressed) { mWidget->unsetCursor(); } } void WidgetData::handleHoverMoveEvent(QHoverEvent* event) { if (mLeftButtonPressed) { return; } if (d_ptr->mWidgetResizable) { updateCursorShape(mWidget->mapToGlobal(event->pos())); } }
5、更新鼠标状态
void WidgetData::updateCursorShape(const QPoint & globalMousePos) { if (mWidget->isFullScreen() || mWidget->isMaximized()) { if (mCursorShapeChanged) { mWidget->unsetCursor(); } return; } mMoveMousePos.recalculate(globalMousePos, mWidget->frameGeometry()); if (mMoveMousePos.onTopLeftEdge || mMoveMousePos.onBottomRightEdge) { mWidget->setCursor(Qt::SizeFDiagCursor); mCursorShapeChanged = true; } else if (mMoveMousePos.onTopRightEdge || mMoveMousePos.onBottomLeftEdge) { mWidget->setCursor(Qt::SizeBDiagCursor); mCursorShapeChanged = true; } else if (mMoveMousePos.onLeftEdge || mMoveMousePos.onRightEdge) { mWidget->setCursor(Qt::SizeHorCursor); mCursorShapeChanged = true; } else if (mMoveMousePos.onTopEdge || mMoveMousePos.onBottomEdge) { mWidget->setCursor(Qt::SizeVerCursor); mCursorShapeChanged = true; } else { if (mCursorShapeChanged)//修改鼠标状态 { mWidget->unsetCursor(); mCursorShapeChanged = false; } } }
6、修改窗口大小和移动位置
void WidgetData::resizeWidget(const QPoint& globalMousePos) { QRect origRect; if (d_ptr->mUseRubberBandOnResize) { origRect = mRubberBand->frameGeometry(); } else { origRect = mWidget->frameGeometry(); } int left = origRect.left(); int top = origRect.top(); int right = origRect.right(); int bottom = origRect.bottom(); origRect.getCoords(&left, &top, &right, &bottom); int minWidth = mWidget->minimumWidth(); int minHeight = mWidget->minimumHeight(); if (mPressedMousePos.onTopLeftEdge) { left = globalMousePos.x(); top = globalMousePos.y(); } else if (mPressedMousePos.onBottomLeftEdge) { left = globalMousePos.x(); bottom = globalMousePos.y(); } else if (mPressedMousePos.onTopRightEdge) { right = globalMousePos.x(); top = globalMousePos.y(); } else if (mPressedMousePos.onBottomRightEdge) { right = globalMousePos.x(); bottom = globalMousePos.y(); } else if (mPressedMousePos.onLeftEdge) { left = globalMousePos.x(); int max_width = mWidget->maximumWidth(); if (right - left > max_width) { return; } } else if (mPressedMousePos.onRightEdge) { right = globalMousePos.x(); } else if (mPressedMousePos.onTopEdge) { top = globalMousePos.y(); } else if (mPressedMousePos.onBottomEdge) { bottom = globalMousePos.y(); } QRect newRect(QPoint(left, top), QPoint(right, bottom)); if (newRect.isValid()) { if (minWidth > newRect.width()) { //determine what has caused the width change. if (left != origRect.left()) newRect.setLeft(origRect.left()); else newRect.setRight(origRect.right()); } if (minHeight > newRect.height()) { //determine what has caused the height change. if (top != origRect.top()) newRect.setTop(origRect.top()); else newRect.setBottom(origRect.bottom()); } if (d_ptr->mUseRubberBandOnResize) { if (mRubberBand->isVisible() == false) { mRubberBand->show(); } mRubberBand->setGeometry(newRect); } else { //mWidget->setGeometry(newRect); mWidget->move(newRect.topLeft()); mWidget->resize(newRect.size()); } } else { //qDebug() << "Calculated Rect is not valid" << newRect; } } void WidgetData::moveWidget(const QPoint & globalMousePos) { bool canMove = false; if (d_ptr->mLocalOnMove == true && d_ptr->mCanMoveFlag != true) { for (int i = ; i < d_ptr->mLocalWidget.size(); ++i) { if (d_ptr->mLocalWidget[i]->rect().contains(d_ptr->mLocalWidget[i]->mapFromGlobal(globalMousePos))) { canMove = true; d_ptr->mCanMoveFlag = true; break; } } } else { canMove = true; } if (canMove) { if (d_ptr->mUseRubberBandOnMove) { if (mRubberBand->isVisible() == false) { mRubberBand->show(); } mRubberBand->move(globalMousePos - mDragPos); } else { mWidget->move(globalMousePos - mDragPos); } } }
qt 拖拽 修改大小(二)的更多相关文章
- qt 拖拽 修改大小
写次篇文章之前,qt窗口的放大缩小和拖拽我都是通过setGeometry方法实现的,但是作为windows程序,windows支持橡 皮筋式(拖拽时有一个虚框)拖拽和拉伸.通过setGeometry方 ...
- qt 拖拽 修改大小(使用了nativeEvent和winEvent)
http://www.cnblogs.com/swarmbees/p/5621543.html http://blog.sina.com.cn/s/blog_9e59cf590102w3r6.html
- Qt::QWidget 无默认标题栏边框的拖拽修改大小方式
开发环境:win10+vs2015+qt5.9.1 背景:开发过程中,一般很少会使用系统提供的标题栏和边框:往往都是自定义一个自己设计的方案.这时候在QWidget中需要加上flag:Qt::Fram ...
- Qt拖拽界面 (*.ui) 缩放问题及解决办法
问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...
- canvas 图片拖拽旋转之二——canvas状态保存(save和restore)
引言 在上一篇日志“canvas 图片拖拽旋转之一”中,对坐标转换有了比较深入的了解,但是仅仅利用坐标转换实现的拖拽旋转,会改变canvas坐标系的状态,从而影响画布上其他元素的绘制.因此,这个时候需 ...
- 解决Delphi图形化界面的TEdit、TLable等组件手动拖拽固定大小,但是编译之后显示有差别的情况
经常遇到这样的情况,在我们使用Delphi的可视化工具进行UI设计的时候,我们拖拽TEdit或者Label组件,并且在可视化界面上设置它们的长.宽 但是当我们编译和运行程序的时候,却发现真正显示出来的 ...
- Qt拖拽界面 (*.ui) 缩放问题及解决办法(在最顶层放一个Layout)
问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...
- JavaScript小实例:拖拽应用(二)
经常在网站别人的网站的注册页中看到一个拖拽验证的效果,就是它的验证码刚开始不出来,而是有一个拖拽的条,你必须将这个拖拽条拖到底,验证码才出来,说了感觉跟没说一样,你还是不理解,好吧,我给个图你看看: ...
- react之每日一更(实现canvas拖拽,增、删、改拖拽模块大小功能)
效果图: import React, { Component } from 'react'; import scaleImage from './images/scale.png'; import c ...
随机推荐
- 通过javap终极理解++i和i++的区别
一直在学习Java,碰到了很多问题,碰到了很多关于i++和++i的难题,以及最经典的String str = "abc" 共创建了几个对象的疑难杂症. 知道有一日知道了java的反 ...
- VMware手动添加centos7硬盘图文操作及分区超详细
先设置虚拟机 启动的虚拟机,新关机再设置 1.选择指定虚拟机,点击硬盘 2.虚拟机设置,点击左下角“添加” 3.硬件类型选择硬盘,点击下一步 4.添加硬件向导默认就行,下一步 5.选择磁盘,默认选中, ...
- 微信小程序之canvas绘制海报分享到朋友圈
绘制canvas内容 首先,需要写一个canvas标签,给canvas-id命名为shareBox <canvas canvas-id="shareBox"></ ...
- Flex布局—语法篇
网页布局(layout)是CSS的一个重点应用. 布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂直居中 ...
- 非vue-cli的花括号闪现问题
<div id="app" v-cloak></div>[v-cloak] { display: none;}这种方式可以解决网速较慢,vue.js文件还没 ...
- web 12
调用一个地图(百度地图)API(定位) 到网站: 1.调用API的js : <script type="text/javascript" src="https:// ...
- WebService常用接口链接(很全面,值得一看)
天气预报Web服务,数据来源于中国气象局Endpoint :http://www.webxml.com.cn/WebServices/WeatherWebService.asmxDisco ...
- Idea集成maven插件
学习目标 1.正确在idea上安装maven 2.安装后使用的基本操作 3.回顾安装步骤 安装过程 设置安装后自动下载功能 maven一键构建概念 我们的项目,往往都要经历编译. 测试. 运行. 打包 ...
- ES6新增对象方法的访问描述符:get(只读)、set(只写)
Es6新增对象方法的访问描述符:get(只读).set(只写),可以直接使用,一般用于数据监听,用途类似于vue.$watch. var obj = { a:1, get bar() { return ...
- getMemory的经典例子
//NO.1:程序首先申请一个char类型的指针str,并把str指向NULL(即str里存的是NULL的地址,*str为NULL中的值为0),调用函数的过程中做了如下动作:1申请一个char类型的指 ...