最近项目需要实现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方法的实现请看另一篇文章,不过在我使用的过程中,使用了HTCAPTION这个属性后,原始窗口的双击放大事件被屏蔽掉了,到现在原因未搞清。在qt 拖拽 修改大小这篇文字中提到的bug,我用迂回的方式解决了,那就是使用Qt::WindowSystemMenuHint属性,但是窗口的放大和缩小使用另一种方式解决。
下面就是我使用代理的方式来支持窗口拖拽,由于该代理代码行数过多,我只写下重点的部分,该代理代码我也是从别人那儿拷贝的,后来根据我自己的理解和项目需求添加了一些东西。
代理头文件
1 #ifndef NC_FRAMELESS_HELPER_H 2 3 #define NC_FRAMELESS_HELPER_H 4 5 #include 6 7 #include 8 9 #include 10 11 #include "commonControls/include/commoncontrols_global.h" 12 13 class WidgetResizeHandlerImpl; 14 15 class CRubberBand : public QRubberBand 16 17 { 18 19 public: 20 21 CRubberBand(QRubberBand::Shape s, QWidget * parent = nullptr); 22 23 ~CRubberBand(); 24 25 protected: 26 27 virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; 28 29 virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; 30 31 void changeEvent(QEvent *) Q_DECL_OVERRIDE; 32 33 void showEvent(QShowEvent *) Q_DECL_OVERRIDE; 34 35 void moveEvent(QMoveEvent *) Q_DECL_OVERRIDE; 36 37 private: 38 39 }; 40 41 //鼠标状态,可以获取鼠标当前和目标窗口的关系 42 43 class CursorPosCalculator 44 45 { 46 47 public: 48 49 CursorPosCalculator(){ reset(); } 50 51 void reset(); 52 53 void recalculate(const QPoint& globalMousePos, const QRect& frameRect); 54 55 public: 56 57 bool onEdges; 58 59 bool onLeftEdge; 60 61 bool onRightEdge; 62 63 bool onTopEdge; 64 65 bool onBottomEdge; 66 67 bool onTopLeftEdge; 68 69 bool onBottomLeftEdge; 70 71 bool onTopRightEdge; 72 73 bool onBottomRightEdge; 74 75 static int mBorderWidth; 76 77 }; 78 79 //真正的处理操作类 80 81 class WidgetData 82 83 { 84 85 public: 86 87 WidgetData(WidgetResizeHandlerImpl * d, QWidget* topLevelWidget); 88 89 ~WidgetData(); 90 91 QWidget * widget(); 92 93 void handleWidgetEvent(QEvent * event);//处理指定窗口事件入口函数 94 95 void updateRubberBandStatus(); 96 97 private: 98 99 void updateCursorShape(const QPoint& globalMousePos);100 101 void resizeWidget(const QPoint& globalMousePos);102 103 void moveWidget(const QPoint& globalMousePos);104 105 void handleMousePressEvent(QMouseEvent* event);106 107 void handleMouseReleaseEvent(QMouseEvent* event);108 109 void handleMouseMoveEvent(QMouseEvent* event);110 111 void handleLeaveEvent(QEvent* event);112 113 void handleHoverMoveEvent(QHoverEvent* event);114 115 private:116 117 bool mLeftButtonPressed = false;118 119 bool mCursorShapeChanged = false;120 121 Qt::WindowFlags mWindowFlags;122 123 QPoint mDragPos;//拖拽位置起点124 125 QWidget * mWidget = nullptr;//被代理的窗口指针126 127 CRubberBand * mRubberBand = nullptr;//橡胶类,支持橡胶操作128 129 CursorPosCalculator mPressedMousePos;//鼠标按下时光标信息130 131 CursorPosCalculator mMoveMousePos;//鼠标移动时光标信息132 133 WidgetResizeHandlerImpl * d_ptr;134 135 };136 137 ///说明:当QWidget设置了Qt::FramelessWindowHint属性时,可以借助该类完成:拖拽+窗口大小更改138 139 class COMMONCONTROLS_EXPORT WidgetResizeHandler : public QObject140 141 {142 143 public:144 145 explicit WidgetResizeHandler(QObject* parent = 0);146 147 ~WidgetResizeHandler();148 149 public:150 151 void activateOn(QWidget * topLevelWidget);//添加topLevelWidget事件代理152 153 void removeFrom(QWidget * topLevelWidget);//移除topLevelWidget事件代理154 155 Qt::CursorShape CursorShape(QWidget * widget);156 157 //窗口移动 default:true158 159 void setWidgetMovable(bool movable);160 161 bool isWidgetMovable();162 163 //大小可变 default:true164 165 void setWidgetResizable(bool resizable);166 167 bool isWidgetResizable();168 169 // 橡胶式窗口移动 default:false170 171 void useRubberBandOnMove(bool use);172 173 bool isUsingRubberBandOnMove();174 175 //橡胶式修改大小 default:false176 177 void useRubberBandOnResize(bool use);178 179 bool isUsingRubberBandOnResisze();180 181 void setBorderWidth(int newBorderWidth);182 183 int borderWidth();184 185 //局部可移动186 187 void useLocalMoveabled(bool use);188 189 void addLocalWidget(QWidget *);190 191 protected:192 193 virtual bool eventFilter(QObject * obj, QEvent * event) Q_DECL_OVERRIDE;//?????????????????????????????????????194 195 private:196 197 WidgetResizeHandlerImpl * d_ptr;198 199 };200 201 #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构造函数
1 CRubberBand::CRubberBand(QRubberBand::Shape s, QWidget * parent) :QRubberBand(QRubberBand::Rectangle, parent) 2 3 { 4 5 setAttribute(Qt::WA_TranslucentBackground); 6 7 #ifndef Q_DEAD_CODE_FROM_QT4_WIN 8 9 setAttribute(Qt::WA_NoSystemBackground);10 11 #endif //Q_DEAD_CODE_FROM_QT4_WIN12 13 setWindowFlags(windowFlags() | Qt::FramelessWindowHint);14 15 }
1 WidgetData::WidgetData(WidgetResizeHandlerImpl * d, QWidget * topLevelWidget) 2 3 { 4 5 this->d_ptr = d; 6 7 mWidget = topLevelWidget; 8 9 mWidget->setMouseTracking(true);10 11 mWindowFlags = mWidget->windowFlags();12 13 //mWindowFlags |= Qt::CustomizeWindowHint | Qt::FramelessWindowHint;14 15 mWindowFlags |= Qt::FramelessWindowHint;16 17 mWidget->setWindowFlags(mWindowFlags);18 19 //mWidget->setWindowFlags( Qt::Popup | Qt::CustomizeWindowHint|Qt::FramelessWindowHint );20 21 //Bug fix, mouse move events does not propagate from child widgets.22 23 //so need the hover events.24 25 mWidget->setAttribute(Qt::WA_Hover);26 27 updateRubberBandStatus();28 29 bool visible = mWidget->isVisible();//防止非widget被代理30 31 mWidget->setVisible(visible);32 33 }
1 void WidgetData::handleWidgetEvent(QEvent * event) 2 3 { 4 5 switch (event->type()) 6 7 { 8 9 case QEvent::MouseButtonPress:10 11 handleMousePressEvent(static_cast(event));12 13 break;14 15 case QEvent::MouseButtonRelease:16 17 handleMouseReleaseEvent(static_cast(event));18 19 break;20 21 case QEvent::MouseMove:22 23 handleMouseMoveEvent(static_cast(event));24 25 break;26 27 case QEvent::Leave:28 29 handleLeaveEvent(event);30 31 break;32 33 case QEvent::HoverMove:34 35 handleHoverMoveEvent(static_cast(event));36 37 break;38 39 }40 41 }
1 void WidgetData::handleMousePressEvent(QMouseEvent * event) 2 3 { 4 5 if (event->button() == Qt::LeftButton) 6 7 { 8 9 mLeftButtonPressed = true; 10 11 QRect frameRect = mWidget->frameGeometry(); 12 13 mPressedMousePos.recalculate(event->globalPos(), frameRect); 14 15 mDragPos = event->globalPos() - frameRect.topLeft(); 16 17 if (mPressedMousePos.onEdges) 18 19 { 20 21 if (d_ptr->mUseRubberBandOnResize) 22 23 { 24 25 mRubberBand->setGeometry(frameRect); 26 27 //mRubberBand->show(); 28 29 } 30 31 } 32 33 else if (d_ptr->mUseRubberBandOnMove) 34 35 { 36 37 mRubberBand->setGeometry(frameRect); 38 39 //mRubberBand->show(); 40 41 } 42 43 if (d_ptr->mLocalOnMove)//启用局部拖拽功能后 需要处理不在指定范围内的拖拽,并过滤掉 44 45 { 46 47 bool canMove = false; 48 49 for (int i = 0; i < d_ptr->mLocalWidget.size(); ++i) 50 51 { 52 53 if (d_ptr->mLocalWidget[i]->rect().contains(d_ptr->mLocalWidget[i]->mapFromGlobal(event->globalPos()))) 54 55 { 56 57 canMove = true; 58 59 break; 60 61 } 62 63 } 64 65 if (canMove == false && mPressedMousePos.onEdges == false) 66 67 { 68 69 mLeftButtonPressed = false; 70 71 } 72 73 } 74 75 } 76 77 } 78 79 void WidgetData::handleMouseReleaseEvent(QMouseEvent* event) 80 81 { 82 83 if (event->button() == Qt::LeftButton) 84 85 { 86 87 d_ptr->mCanMoveFlag = false; 88 89 mLeftButtonPressed = false; 90 91 mPressedMousePos.reset(); 92 93 if (mRubberBand && mRubberBand->isVisible()) 94 95 { 96 97 mRubberBand->hide(); 98 99 mWidget->setGeometry(mRubberBand->geometry());100 101 }102 103 }104 105 }106 107 void WidgetData::handleMouseMoveEvent(QMouseEvent* event)108 109 {110 111 if (mLeftButtonPressed)112 113 {114 115 if (d_ptr->mWidgetResizable && mPressedMousePos.onEdges)116 117 {118 119 resizeWidget(event->globalPos());120 121 }122 123 else if (d_ptr->mWidgetMovable)124 125 {126 127 moveWidget(event->globalPos());128 129 }130 131 }132 133 else if (d_ptr->mWidgetResizable)134 135 {136 137 updateCursorShape(event->globalPos());138 139 }140 141 }142 143 void WidgetData::handleLeaveEvent(QEvent*)144 145 {146 147 if (!mLeftButtonPressed)148 149 {150 151 mWidget->unsetCursor();152 153 }154 155 }156 157 void WidgetData::handleHoverMoveEvent(QHoverEvent* event)158 159 {160 161 if (mLeftButtonPressed)162 163 {164 165 return;166 167 }168 169 if (d_ptr->mWidgetResizable)170 171 {172 173 updateCursorShape(mWidget->mapToGlobal(event->pos()));174 175 }176 177 }
5、更新鼠标状态
1 void WidgetData::updateCursorShape(const QPoint & globalMousePos) 2 3 { 4 5 if (mWidget->isFullScreen() || mWidget->isMaximized()) 6 7 { 8 9 if (mCursorShapeChanged)10 11 {12 13 mWidget->unsetCursor();14 15 }16 17 return;18 19 }20 21 mMoveMousePos.recalculate(globalMousePos, mWidget->frameGeometry());22 23 if (mMoveMousePos.onTopLeftEdge || mMoveMousePos.onBottomRightEdge)24 25 {26 27 mWidget->setCursor(Qt::SizeFDiagCursor);28 29 mCursorShapeChanged = true;30 31 }32 33 else if (mMoveMousePos.onTopRightEdge || mMoveMousePos.onBottomLeftEdge)34 35 {36 37 mWidget->setCursor(Qt::SizeBDiagCursor);38 39 mCursorShapeChanged = true;40 41 }42 43 else if (mMoveMousePos.onLeftEdge || mMoveMousePos.onRightEdge)44 45 {46 47 mWidget->setCursor(Qt::SizeHorCursor);48 49 mCursorShapeChanged = true;50 51 }52 53 else if (mMoveMousePos.onTopEdge || mMoveMousePos.onBottomEdge)54 55 {56 57 mWidget->setCursor(Qt::SizeVerCursor);58 59 mCursorShapeChanged = true;60 61 }62 63 else64 65 {66 67 if (mCursorShapeChanged)//修改鼠标状态68 69 {70 71 mWidget->unsetCursor();72 73 mCursorShapeChanged = false;74 75 }76 77 }78 79 }
1 void WidgetData::resizeWidget(const QPoint& globalMousePos) 2 3 { 4 5 QRect origRect; 6 7 if (d_ptr->mUseRubberBandOnResize) 8 9 { 10 11 origRect = mRubberBand->frameGeometry(); 12 13 } 14 15 else 16 17 { 18 19 origRect = mWidget->frameGeometry(); 20 21 } 22 23 int left = origRect.left(); 24 25 int top = origRect.top(); 26 27 int right = origRect.right(); 28 29 int bottom = origRect.bottom(); 30 31 origRect.getCoords(&left, &top, &right, &bottom); 32 33 int minWidth = mWidget->minimumWidth(); 34 35 int minHeight = mWidget->minimumHeight(); 36 37 if (mPressedMousePos.onTopLeftEdge) 38 39 { 40 41 left = globalMousePos.x(); 42 43 top = globalMousePos.y(); 44 45 } 46 47 else if (mPressedMousePos.onBottomLeftEdge) 48 49 { 50 51 left = globalMousePos.x(); 52 53 bottom = globalMousePos.y(); 54 55 } 56 57 else if (mPressedMousePos.onTopRightEdge) 58 59 { 60 61 right = globalMousePos.x(); 62 63 top = globalMousePos.y(); 64 65 } 66 67 else if (mPressedMousePos.onBottomRightEdge) 68 69 { 70 71 right = globalMousePos.x(); 72 73 bottom = globalMousePos.y(); 74 75 } 76 77 else if (mPressedMousePos.onLeftEdge) 78 79 { 80 81 left = globalMousePos.x(); 82 83 int max_width = mWidget->maximumWidth(); 84 85 if (right - left > max_width) 86 87 { 88 89 return; 90 91 } 92 93 } 94 95 else if (mPressedMousePos.onRightEdge) 96 97 { 98 99 right = globalMousePos.x();100 101 }102 103 else if (mPressedMousePos.onTopEdge)104 105 {106 107 top = globalMousePos.y();108 109 }110 111 else if (mPressedMousePos.onBottomEdge)112 113 {114 115 bottom = globalMousePos.y();116 117 }118 119 QRect newRect(QPoint(left, top), QPoint(right, bottom));120 121 if (newRect.isValid())122 123 {124 125 if (minWidth > newRect.width())126 127 {128 129 //determine what has caused the width change.130 131 if (left != origRect.left())132 133 newRect.setLeft(origRect.left());134 135 else136 137 newRect.setRight(origRect.right());138 139 }140 141 if (minHeight > newRect.height())142 143 {144 145 //determine what has caused the height change.146 147 if (top != origRect.top())148 149 newRect.setTop(origRect.top());150 151 else152 153 newRect.setBottom(origRect.bottom());154 155 }156 157 if (d_ptr->mUseRubberBandOnResize)158 159 {160 161 if (mRubberBand->isVisible() == false)162 163 {164 165 mRubberBand->show();166 167 }168 169 mRubberBand->setGeometry(newRect);170 171 }172 173 else174 175 {176 177 //mWidget->setGeometry(newRect);178 179 mWidget->move(newRect.topLeft());180 181 mWidget->resize(newRect.size());182 183 }184 185 }186 187 else188 189 {190 191 //qDebug() << "Calculated Rect is not valid" << newRect;192 193 }194 195 }196 197 void WidgetData::moveWidget(const QPoint & globalMousePos)198 199 {200 201 bool canMove = false;202 203 if (d_ptr->mLocalOnMove == true && d_ptr->mCanMoveFlag != true)204 205 {206 207 for (int i = 0; i < d_ptr->mLocalWidget.size(); ++i)208 209 {210 211 if (d_ptr->mLocalWidget[i]->rect().contains(d_ptr->mLocalWidget[i]->mapFromGlobal(globalMousePos)))212 213 {214 215 canMove = true;216 217 d_ptr->mCanMoveFlag = true;218 219 break;220 221 }222 223 }224 225 }226 227 else228 229 {230 231 canMove = true;232 233 }234 235 if (canMove)236 237 {238 239 if (d_ptr->mUseRubberBandOnMove)240 241 {242 243 if (mRubberBand->isVisible() == false)244 245 {246 247 mRubberBand->show();248 249 }250 251 mRubberBand->move(globalMousePos - mDragPos);252 253 }254 255 else256 257 {258 259 mWidget->move(globalMousePos - mDragPos);260 261 }262 263 }264 265 }