Qt Window Drag and Resize with Rubber Band Effect

Recently, the project required implementing a rubber band affect on Windows, so I did some research and want to share it.

Windows system supports the rubber band effect, which can be achieved using Win32 methods: SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, showFullWindow, NULL, 0); showFullWindow is a variable. If you need the default Windows rubber band effect, pass false, othrewise pass true. When using the default rubber band resizing, a rectangular frame appears, which affects both window movement and resizing. When the mouse is released, the real window moves or resizes. If not using the rubber band drag method, the window will move in real-time.

Figure 1: Windows Rubber Band

When using Qt windows, if you want to support this drag method on Windows, you cannot use setGeometry to move or resize the window. Instead, you need to override QWidget::nativeEvent, which is called before messages enter the Qt event loop. This means that nativeEvent is called before mouseEvent methods. For the implementation of nativeEvent, please refer to another article titled "Qt Drag and Resize." However, during my usage, after using HTCAPTION, the original double-click maximize event was blocked. The reason is still unknown. In the article "Qt Drag and Resize," a bug was mentioned, and I solved it by using Qt::WindowSystemMenuHint, but for window resizing, I used another approach.

Below is the proxy code to support window dragging. Since the proxy code is lengthy, I only write the key parts. This proxy code was also copied from someone else and modified based on my understanding and project requirements.

Proxy Header File

  1 #ifndef NC_FRAMELESS_HELPER_H
  2 
  3 #define NC_FRAMELESS_HELPER_H
  4 
  5 #include <QObject>
  6 
  7 #include <QWidget>
  8 
  9 #include <QRubberBand>
 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 // Mouse state, can get the relationship between current cursor and target window
 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 // The actual processing class
 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);// Entry point for handling specific window events
 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;// Start position of drag
124 
125     QWidget * mWidget = nullptr;// Pointer to the widget being proxied
126 
127     CRubberBand * mRubberBand = nullptr;// Rubber band class, supports rubber band operations
128 
129     CursorPosCalculator mPressedMousePos;// Mouse information when pressed
130 
131     CursorPosCalculator mMoveMousePos;// Mouse information when moving
132 
133     WidgetResizeHandlerImpl * d_ptr;
134 
135 };
136 
137 /// Description: When QWidget has Qt::FramelessWindowHint attribute, this class can complete: drag + window size change
138 
139 class COMMONCONTROLS_EXPORT WidgetResizeHandler : public QObject
140 
141 {
142 
143 public:
144 
145     explicit WidgetResizeHandler(QObject* parent = 0);
146 
147     ~WidgetResizeHandler();
148 
149 public:
150 
151     void activateOn(QWidget * topLevelWidget);// Add top level widget event proxy
152 
153     void removeFrom(QWidget * topLevelWidget);// Remove top level widget event proxy
154 
155     Qt::CursorShape CursorShape(QWidget * widget);
156 
157     // Window movement default: true
158 
159     void setWidgetMovable(bool movable);
160 
161     bool isWidgetMovable();
162 
163     // Size change default: true
164 
165     void setWidgetResizable(bool resizable);
166 
167     bool isWidgetResizable();
168 
169     // Rubber band window movement default: false
170 
171     void useRubberBandOnMove(bool use);
172 
173     bool isUsingRubberBandOnMove();
174 
175     // Rubber band size change default: false
176 
177     void useRubberBandOnResize(bool use);
178 
179     bool isUsingRubberBandOnResisze();
180 
181     void setBorderWidth(int newBorderWidth);
182 
183     int borderWidth();
184 
185     // Local movable
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

View CodeMost of the comments in the header file are basic, so I won't explain them further. Next, I'll explain the principles. In the class that needs to be proxied, declare a WidgetResizeHandler object and use the activateOn method to add the window to be proxied. Note that the proxied window must have the Qt::Window attribute, meaning it must be a top-level window. When proxying a complex window, unexpected issues may arise, such as QLabel receiving rich text and not getting the mouse release event, or QToolButton not being in the layout and not getting the mouse release event, which can cause the proxy to malfunction. Therefore, I modified the proxy and added the useLocalMoveabled interface to allow the proxy to only move local windows, solving the two problems I mentioned earlier. If you look closely, you can see that my proxy also uses the setGeometry method to drag the window, which differs from what I discussed about the rubber band method. Now comes the key part, hahaha, continue reading. In the header file, there's a class CRubberBand, which inherits from QRubberBand. This class simulates a rubber band process, but the interface provided by Qt is limited. Some features are difficult to achieve, such as implementing a complex proxy interface, where we can only draw ourselves. I re-implemented the paintEvent function to draw a gray rectangle, as shown below:

QPainter p(this);

p.setPen(QPen(QColor(102, 102, 102), 4));

QRect rect = this->rect().adjusted(2, 2, -3, -3);

p.drawRect(rect);

If you follow the steps I mentioned, you will find that in addition to a rectangle, there is also a background color fill. This is strange because our paintEvent does not draw the background. Haha, just add this line in the constructor: setAttribute(Qt::WA_NoSystemBackground). The result is shown in Figure 2.

Figure 2: Custom Rubber Band

Next, I add some proxy code.

  1. CRubberBand constructor
 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_WIN
12 
13     setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
14 
15 }

View Code2. After activating, store and verify the window ``` 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();// prevent non-widget from being proxied 30 31 mWidget->setVisible(visible); 32 33 }


View Code3. When the proxied window has a mouse event, the proxy handles it first ```
 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 }

View Code4. The following five functions are the specific implementations of the above``` 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)// After enabling local drag functionality, need to handle drags outside the specified area and filter them out 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 }


View Code5. Update cursor status


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 else 64 65 { 66 67 if (mCursorShapeChanged)// Modify cursor status 68 69 { 70 71 mWidget->unsetCursor(); 72 73 mCursorShapeChanged = falce; 74 75 } 76 77 } 78 79 }


View Code6. Adjust window size and move position  

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 caused the width change. 130 131 if (left != origRect.left()) 132 133 newRect.setLeft(origRect.left()); 134 135 else 136 137 newRect.setRight(origRect.right()); 138 139 } 140 141 if (minHeight > newRect.height()) 142 143 { 144 145 // Determine what caused the height change. 146 147 if (top != origRect.top()) 148 149 newRect.setTop(origRect.top()); 150 151 else 152 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 else 174 175 { 176 177 //mWidget->setGeometry(newRect); 178 179 mWidget->move(newRect.topLeft()); 180 181 mWidget->resize(newRect.size()); 182 183 } 184 185 } 186 187 else 188 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 else 228 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 else 256 257 { 258 259 mWidget->move(globalMousePos - mDragPos); 260 261 } 262 263 } 264 265 }


View Code

Tags: Qt Window Drag Rubber Band Qt Widgets C++

Posted on Mon, 28 Sep 2026 16:31:57 +0000 by djp120