最近项目需要实现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);

         }

     }

 }

如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!! 

 

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。

qt 拖拽 修改大小(二)的更多相关文章

  1. qt 拖拽 修改大小

    写次篇文章之前,qt窗口的放大缩小和拖拽我都是通过setGeometry方法实现的,但是作为windows程序,windows支持橡 皮筋式(拖拽时有一个虚框)拖拽和拉伸.通过setGeometry方 ...

  2. qt 拖拽 修改大小(使用了nativeEvent和winEvent)

    http://www.cnblogs.com/swarmbees/p/5621543.html http://blog.sina.com.cn/s/blog_9e59cf590102w3r6.html

  3. Qt::QWidget 无默认标题栏边框的拖拽修改大小方式

    开发环境:win10+vs2015+qt5.9.1 背景:开发过程中,一般很少会使用系统提供的标题栏和边框:往往都是自定义一个自己设计的方案.这时候在QWidget中需要加上flag:Qt::Fram ...

  4. Qt拖拽界面 (*.ui) 缩放问题及解决办法

    问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...

  5. canvas 图片拖拽旋转之二——canvas状态保存(save和restore)

    引言 在上一篇日志“canvas 图片拖拽旋转之一”中,对坐标转换有了比较深入的了解,但是仅仅利用坐标转换实现的拖拽旋转,会改变canvas坐标系的状态,从而影响画布上其他元素的绘制.因此,这个时候需 ...

  6. 解决Delphi图形化界面的TEdit、TLable等组件手动拖拽固定大小,但是编译之后显示有差别的情况

    经常遇到这样的情况,在我们使用Delphi的可视化工具进行UI设计的时候,我们拖拽TEdit或者Label组件,并且在可视化界面上设置它们的长.宽 但是当我们编译和运行程序的时候,却发现真正显示出来的 ...

  7. Qt拖拽界面 (*.ui) 缩放问题及解决办法(在最顶层放一个Layout)

    问题 使用Qt Designer 设计的界面,在缩放的时候不能随着主窗口一起缩放. 解决办法 之前遇到这个问题的时候,都是直接重写resizeEvent接口来实现的,在自动生成的Ui_Widget或U ...

  8. JavaScript小实例:拖拽应用(二)

    经常在网站别人的网站的注册页中看到一个拖拽验证的效果,就是它的验证码刚开始不出来,而是有一个拖拽的条,你必须将这个拖拽条拖到底,验证码才出来,说了感觉跟没说一样,你还是不理解,好吧,我给个图你看看: ...

  9. react之每日一更(实现canvas拖拽,增、删、改拖拽模块大小功能)

    效果图: import React, { Component } from 'react'; import scaleImage from './images/scale.png'; import c ...

随机推荐

  1. 通过javap终极理解++i和i++的区别

    一直在学习Java,碰到了很多问题,碰到了很多关于i++和++i的难题,以及最经典的String str = "abc" 共创建了几个对象的疑难杂症. 知道有一日知道了java的反 ...

  2. VMware手动添加centos7硬盘图文操作及分区超详细

    先设置虚拟机 启动的虚拟机,新关机再设置 1.选择指定虚拟机,点击硬盘 2.虚拟机设置,点击左下角“添加” 3.硬件类型选择硬盘,点击下一步 4.添加硬件向导默认就行,下一步 5.选择磁盘,默认选中, ...

  3. 微信小程序之canvas绘制海报分享到朋友圈

    绘制canvas内容 首先,需要写一个canvas标签,给canvas-id命名为shareBox <canvas canvas-id="shareBox"></ ...

  4. Flex布局—语法篇

    网页布局(layout)是CSS的一个重点应用. 布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂直居中 ...

  5. 非vue-cli的花括号闪现问题

    <div id="app" v-cloak></div>[v-cloak] { display: none;}这种方式可以解决网速较慢,vue.js文件还没 ...

  6. web 12

    调用一个地图(百度地图)API(定位) 到网站: 1.调用API的js : <script type="text/javascript" src="https:// ...

  7. WebService常用接口链接(很全面,值得一看)

    天气预报Web服务,数据来源于中国气象局Endpoint :http://www.webxml.com.cn/WebServices/WeatherWebService.asmxDisco       ...

  8. Idea集成maven插件

    学习目标 1.正确在idea上安装maven 2.安装后使用的基本操作 3.回顾安装步骤 安装过程 设置安装后自动下载功能 maven一键构建概念 我们的项目,往往都要经历编译. 测试. 运行. 打包 ...

  9. ES6新增对象方法的访问描述符:get(只读)、set(只写)

    Es6新增对象方法的访问描述符:get(只读).set(只写),可以直接使用,一般用于数据监听,用途类似于vue.$watch. var obj = { a:1, get bar() { return ...

  10. getMemory的经典例子

    //NO.1:程序首先申请一个char类型的指针str,并把str指向NULL(即str里存的是NULL的地址,*str为NULL中的值为0),调用函数的过程中做了如下动作:1申请一个char类型的指 ...