Make a frameless QWidget resizable with QRubberBand ?
-
Hi.
I'm going to add resize functionality to myQWidget
(with flagQt::FramelessWindowHint
) but withoutQSizeGrip
I know that :
I need a
QRubberBand
member
I Should reimplementeventFilter()
installEventFilter()
in my class
HandlemouseEvent
(Hover,Leave,..) + calculate cursor position and then change the cursor shape
resize()
myQWidget
to new drag pointBut here are some problem i face to :
Should i
installEventFilter()
for my class (this
) or myQRubberBand
member?(process the events forQRubberBand
orQwidget
? )
How should i setQRubberBand
to myQWidget
class?(How should useQRubberBand
)
how change theQRubberBand
weight ?(cursor change area)what i have tried already (but nothing happedn !) :
.hclass Sample : public QWidget { Q_OBJECT public: explicit Sample(); ~Sample(); private: QRubberBand *_rubberband; enum Edge { None, Top, Bottom, Left, Right, TopLeft, TopRight, BottomLeft, BottomRight }; Edge _edge; bool _cursorchanged; qint16 _borderWidth; private: void Sample::hoverMoveEvent(QHoverEvent *e); void Sample::leaveMoveEvent(QEvent *e); void Sample::updateCursorShape(const QPoint &pos); void Sample::calculateCursorPosition(const QPoint &pos); protected: virtual bool eventFilter(QObject *o, QEvent *e); };
.cpp
Sample::Sample(): _edge(None), _cursorchanged(false), _borderWidth(5) { setMouseTracking(true); setWindowFlags(Qt::FramelessWindowHint); _rubberband = NULL; installEventFilter(this); } Sample::~Sample() {} bool Sample::eventFilter(QObject *o, QEvent*e) { if (e->type() == QEvent::MouseMove || e->type() == QEvent::HoverMove || e->type() == QEvent::Leave || e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) { switch (e->type()) { default: break; case QEvent::MouseMove: //mouseMoveEvent(static_cast<QMouseEvent*>(e)); break; case QEvent::HoverMove: hoverMoveEvent(static_cast<QHoverEvent*>(e)); break; case QEvent::Leave: leaveMoveEvent(e); break; case QEvent::MouseButtonPress: //mouseButtonPress(static_cast<QMouseEvent*>(e)); break; case QEvent::MouseButtonRelease: //mouseButtonRealese(static_cast<QMouseEvent*>(e)); break; } } return false; } void Sample::hoverMoveEvent(QHoverEvent *e) { updateCursorShape(mapToGlobal(e->pos())); } void Sample::leaveMoveEvent(QEvent *e) { unsetCursor(); } void Sample::updateCursorShape(const QPoint &pos) { if (isFullScreen() || isMaximized()) { unsetCursor(); return; } calculateCursorPosition((pos)); if (_edge == TopLeft || _edge == BottomRight) { setCursor(Qt::SizeFDiagCursor); _cursorchanged = true; } else if (_edge == TopRight || _edge == BottomLeft) { setCursor(Qt::SizeBDiagCursor); _cursorchanged = true; } else if (_edge == Right || _edge == Left) { setCursor(Qt::SizeHorCursor); _cursorchanged = true; } else if (_edge == Top || _edge == Bottom) { setCursor(Qt::SizeVerCursor); _cursorchanged = true; } else { _cursorchanged = false; unsetCursor(); } } void Sample::calculateCursorPosition(const QPoint &pos) { // Calculate cursor position based on frameGeometry() ,pos and _borderWidth }
-
@IMAN4K said:
Hi
If you see the docs
http://doc.qt.io/qt-5/qrubberband.html#details
it shows how rubberband is used.
Its has no drag to resize build in. ( by it self)
It is simply set by the user of the Band.
So in mousePress, we make a band
in mouseMove we set the geometry
and on mouseRelease we use the rect, we just dragged.So you need not to use eventfilter since you can override the mouseXXX functions
and handle it directly. So if you only have one custom widget, no real need to use
filters.But, you must implement the resize your self. for your widget. The Rubberband
is only for visual clues to uses.So when user Press and Drag in corner of widget, you must adjust the Geometry to match.
and also update the Band so it still fits. -
@mrjj
Thanks for Attention.
I get all you say and know what to do but :
My Widget is actually a base Widget for a Custom Window so it has a Titlebar and i thinkeventFilter()
is good. isn't it ?
Another thing is about cursor changing.i need a space area (just say weight of 5) around my Widget so when Hover happen (and leave also) cursor must be change.
How to achieve this? -
-
and i think eventFilter() is good. isn't it ?
Its fine and ok to use if u prefer over the mouseX methods. -
.i need a space area (just say weight of 5) around my Widget
One way to do this is via a layout. IF you add layout to window, and set
contentsMargins u can get such free area.
http://doc.qt.io/qt-5/qlayout.html#contentsMargins -
-
@IMAN4K
Hi
Im not sure of the issue
if mouse goes into area, u set it, if it leaves are u reset.
You can check in mouseMove if it leaves the area moving further into the widget.so im not sure what ", all around my widget cursor stay the same"
means ? -
@mrjj said:
One way to do this is via a layout. IF you add layout to window, and set
contentsMargins u can get such free areaI'm confusing about this solution.
That require area i have mentioned must receivemouseEvents
and you said you must set a Layout so Can Layouts receivemouseEvent
?@mrjj said:
so im not sure what ", all around my widget cursor stay the same"
means ?I mean if i implement
mouseEvent
for my widget after hovering cursor will change at the edge of my widget
it's good but if keep moving to widget(not leave the widget area) cursor will stay the same and will not change while cursor must return to arrow state if it pass that weight (5)I hope i have been clear.
-
-
Can Layouts receive mouseEvent ?
They should go to the Window Widget. or class that handles the
cursor change. -
cursor must return to arrow state if it pass that weight (5)
I guess detecting it by using mouseMove is too unreliable.
have you check if
http://doc.qt.io/qt-5/qwidget.html#leaveEvent
could be used? -
-
@mrjj
I think i finally figured it out ;)
But can you look at my code please :class Widget : public QWidget { Q_OBJECT public: explicit Widget(); ~Widget(); void setBorderWidth(const qint16 &borderWidth); private: enum Edge { None, Top, Bottom, Left, Right, TopLeft, TopRight, BottomLeft, BottomRight }; QRubberBand *_rubberband; bool _cursorchanged; bool _leftButtonPressed; qint16 _borderWidth; Edge _edge; private: void mouseHover(QHoverEvent *e); void mouseLeave(QEvent *e); void mousePress(QMouseEvent *e); void mouseRealese(QMouseEvent *e); void mouseMove(QMouseEvent *e); void updateCursorShape(const QPoint &pos); void calculateCursorPosition(const QPoint &pos); void updateRubberBand(); protected: void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; bool eventFilter(QObject *o, QEvent *e) Q_DECL_OVERRIDE; };
Widget::Widget() : _cursorchanged(false), _borderWidth(4), _edge(None), _leftButtonPressed(false), _rubberband(0) { setMouseTracking(true); setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint); setAttribute(Qt::WA_Hover); setAttribute(Qt::WA_TranslucentBackground); installEventFilter(this); } Widget::~Widget() {} void Widget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setBrush(QColor("#FFFFFF")); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(QRect(0, 0, width(), height()), 5.0, 5.0); painter.drawPath(path.simplified()); } bool Widget::eventFilter(QObject *o, QEvent*e) { if (e->type() == QEvent::MouseMove || e->type() == QEvent::HoverMove || e->type() == QEvent::Leave || e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) { switch (e->type()) { default: break; case QEvent::MouseMove: mouseMove(static_cast<QMouseEvent*>(e)); break; case QEvent::HoverMove: mouseHover(static_cast<QHoverEvent*>(e)); break; case QEvent::Leave: mouseLeave(e); break; case QEvent::MouseButtonPress: mousePress(static_cast<QMouseEvent*>(e)); break; case QEvent::MouseButtonRelease: mouseRealese(static_cast<QMouseEvent*>(e)); break; } } return false; } void Widget::mouseHover(QHoverEvent *e) { updateCursorShape(mapToGlobal(e->pos())); } void Widget::mouseLeave(QEvent *e) { Q_UNUSED(e); unsetCursor(); } void Widget::mousePress(QMouseEvent *e) { if (e->buttons() && Qt::LeftButton) { _leftButtonPressed = true; if (_edge != None) { updateRubberBand(); _rubberband->setGeometry(frameGeometry().x(), frameGeometry().y(), frameGeometry().width(), frameGeometry().height()); } } } void Widget::mouseRealese(QMouseEvent *e) { _leftButtonPressed = false; _edge = None; if (_rubberband) { updateRubberBand(); } } void Widget::mouseMove(QMouseEvent *e) { if (_leftButtonPressed && _edge != None) { QRect originalRect = _rubberband->frameGeometry(); int left = originalRect.left(); int top = originalRect.top(); int right = originalRect.right(); int bottom = originalRect.bottom(); if (_edge == Top) { top = e->globalPos().y(); } else if (_edge == Bottom) { bottom = e->globalPos().y(); } else if (_edge == Left) { left = e->globalPos().x(); } else if (_edge == Right) { right = e->globalPos().x(); } else if (_edge == TopLeft) { left = e->globalPos().x(); top = e->globalPos().y(); } else if (_edge == TopRight) { top = e->globalPos().y(); right = e->globalPos().x(); } else if (_edge == BottomLeft) { left = e->globalPos().x(); bottom = e->globalPos().y(); } else if (_edge == BottomRight) { bottom = e->globalPos().y(); right = e->globalPos().x(); } _rubberband->setGeometry(QRect(QPoint(left, top), QPoint(right, bottom))); setGeometry(_rubberband->geometry()); } } void Widget::updateCursorShape(const QPoint &pos) { if (isFullScreen() || isMaximized()) { if (_cursorchanged) { unsetCursor(); return; } } calculateCursorPosition((pos)); if (_edge == TopLeft || _edge == BottomRight) { setCursor(Qt::SizeFDiagCursor); _cursorchanged = true; } else if (_edge == TopRight || _edge == BottomLeft) { setCursor(Qt::SizeBDiagCursor); _cursorchanged = true; } else if (_edge == Right || _edge == Left) { setCursor(Qt::SizeHorCursor); _cursorchanged = true; } else if (_edge == Top || _edge == Bottom) { setCursor(Qt::SizeVerCursor); _cursorchanged = true; } else if (_cursorchanged) { _cursorchanged = false; unsetCursor(); } } void Widget::calculateCursorPosition(const QPoint &pos) { bool onLeft = pos.x() <= frameGeometry().x() + _borderWidth && pos.x() >= frameGeometry().x() && pos.y() <= frameGeometry().y() + frameGeometry().height() - _borderWidth && pos.y() >= frameGeometry().y() + _borderWidth; bool onRight = pos.x() >= frameGeometry().x() + frameGeometry().width() - _borderWidth && pos.x() <= frameGeometry().x() + frameGeometry().width() && pos.y() >= frameGeometry().y() + _borderWidth && pos.y() <= frameGeometry().y() + frameGeometry().height() - _borderWidth - 2; bool onBottom = pos.x() >= frameGeometry().x() + _borderWidth && pos.x() <= frameGeometry().x() + frameGeometry().width() - _borderWidth - 2 && pos.y() >= frameGeometry().y() + frameGeometry().height() - _borderWidth && pos.y() <= frameGeometry().y() + frameGeometry().height(); bool onTop = pos.x() >= frameGeometry().x() + _borderWidth && pos.x() <= frameGeometry().x() + frameGeometry().width() - _borderWidth && pos.y() >= frameGeometry().y() && pos.y() <= frameGeometry().y() + _borderWidth; bool onBottomleft = pos.x() <= frameGeometry().x() + _borderWidth && pos.x() >= frameGeometry().x() && pos.y() <= frameGeometry().y() + frameGeometry().height() && pos.y() >= frameGeometry().y() + frameGeometry().height() - _borderWidth; bool onBottomRight = pos.x() >= frameGeometry().x() + frameGeometry().width() - _borderWidth && pos.x() <= frameGeometry().x() + frameGeometry().width() && pos.y() >= frameGeometry().y() + frameGeometry().height() - _borderWidth && pos.y() <= frameGeometry().y() + frameGeometry().height(); bool onTopRight = pos.x() >= frameGeometry().x() + frameGeometry().width() - _borderWidth && pos.x() <= frameGeometry().x() + frameGeometry().width() && pos.y() >= frameGeometry().y() && pos.y() <= frameGeometry().y() + _borderWidth; bool onTopLeft = pos.x() >= frameGeometry().x() && pos.x() <= frameGeometry().x() + _borderWidth && pos.y() >= frameGeometry().y() && pos.y() <= frameGeometry().y() + _borderWidth; if (onLeft) { _edge = Left; } else if (onRight) { _edge = Right; } else if (onBottom) { _edge = Bottom; } else if (onTop) { _edge = Top; } else if (onBottomleft) { _edge = BottomLeft; } else if (onBottomRight) { _edge = BottomRight; } else if (onTopRight) { _edge = TopRight; } else if (onTopLeft) { _edge = TopLeft; } else { _edge = None; } } void Widget::updateRubberBand() { if (!_rubberband) { _rubberband = new QRubberBand(QRubberBand::Rectangle); } else { delete _rubberband; _rubberband = 0; } } void Widget::setBorderWidth(const qint16 &borderWidth) { _borderWidth = borderWidth; }
It work but for top edge and left edge it's not working as i expect !
-
Hi
Good work.
So for top/left what happens? -
Ok
First. good job. Seem to work great.
I can see that dragging up seems not to work but
did not spot anything looking at code.Is the code complete?
I can run it if I put in a default project? -
@IMAN4K
Yep. Besides some #include it ran.The reason up dont work is that it adjust the window the wrong way
(y++) so when u drag up, it goes down 1 pixel and then mouse is outside and the drag stops.Not sure where the actual error is yet. Seems to be need minus and not plus
for drag that way. -
Thanks @mrjj for all answers.
You were right calculating was mistake
I just used an inner space for calculating mouse area but you also need to consider an outer area (the green border) :
https://onedrive.live.com/redir?resid=8882D1E3BC0F61AB!7402&authkey=!ACT6umOQeAEcN7A&v=3&ithint=photo%2Cjpg
Here for any one else :frameless.h
:#include <QtWidgets/QWidget> #include <QtWidgets/QRubberBand> #include <QtCore/QObject> #include <QtCore/QEvent> #include <QtCore/QRect> #include <QtCore/QPoint> #include <QtCore/Qt> #include <QtGui/QHoverEvent> #include <QtGui/QMouseEvent> class FrameLess : public QObject { public: enum Edge { None = 0x0, Left = 0x1, Top = 0x2, Right = 0x4, Bottom = 0x8, TopLeft = 0x10, TopRight = 0x20, BottomLeft = 0x40, BottomRight = 0x80, }; Q_ENUM(Edge); Q_DECLARE_FLAGS(Edges, Edge); FrameLess(QWidget *parent); void setBorderWidth(int); int borderWidth() const; QWidget *_parent = nullptr; QRubberBand *_rubberband = nullptr; bool _cursorchanged; bool _leftButtonPressed; Edges _mousePress = Edge::None; Edges _mouseMove = Edge::None; int _borderWidth; QPoint _dragPos; bool _dragStart = false; protected: bool eventFilter(QObject *o, QEvent *e) override; void mouseHover(QHoverEvent*); void mouseLeave(QEvent*); void mousePress(QMouseEvent*); void mouseRealese(QMouseEvent*); void mouseMove(QMouseEvent*); void updateCursorShape(const QPoint &); void calculateCursorPosition(const QPoint &, const QRect &, Edges &); }; Q_DECLARE_OPERATORS_FOR_FLAGS(FrameLess::Edges);
frameless.cpp
:#include "FrameLess.h" FrameLess::FrameLess(QWidget *parent) : _parent(parent), _cursorchanged(false), _leftButtonPressed(false), _borderWidth(5), _dragPos(QPoint()) { _parent->setMouseTracking(true); _parent->setWindowFlags(Qt::FramelessWindowHint); _parent->setAttribute(Qt::WA_Hover); _parent->installEventFilter(this); _rubberband = new QRubberBand(QRubberBand::Rectangle); } bool FrameLess::eventFilter(QObject *o, QEvent*e) { if (e->type() == QEvent::MouseMove || e->type() == QEvent::HoverMove || e->type() == QEvent::Leave || e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) { switch (e->type()) { case QEvent::MouseMove: mouseMove(static_cast<QMouseEvent*>(e)); return true; break; case QEvent::HoverMove: mouseHover(static_cast<QHoverEvent*>(e)); return true; break; case QEvent::Leave: mouseLeave(e); return true; break; case QEvent::MouseButtonPress: mousePress(static_cast<QMouseEvent*>(e)); return true; break; case QEvent::MouseButtonRelease: mouseRealese(static_cast<QMouseEvent*>(e)); return true; break; } } else { return _parent->eventFilter(o, e); } } void FrameLess::mouseHover(QHoverEvent *e) { updateCursorShape(_parent->mapToGlobal(e->pos())); } void FrameLess::mouseLeave(QEvent *e) { if (!_leftButtonPressed) { _parent->unsetCursor(); } } void FrameLess::mousePress(QMouseEvent *e) { if (e->button() & Qt::LeftButton) { _leftButtonPressed = true; calculateCursorPosition(e->globalPos(), _parent->frameGeometry(), _mousePress); if (!_mousePress.testFlag(Edge::None)) { _rubberband->setGeometry(_parent->frameGeometry()); } if (_parent->rect().marginsRemoved(QMargins(borderWidth(),borderWidth(),borderWidth(),borderWidth())).contains(e->pos())) { _dragStart = true; _dragPos = e->pos(); } } } void FrameLess::mouseRealese(QMouseEvent *e) { if (e->button() & Qt::LeftButton) { _leftButtonPressed = false; _dragStart = false; } } void FrameLess::mouseMove(QMouseEvent *e) { if (_leftButtonPressed) { if (_dragStart) { _parent->move(_parent->frameGeometry().topLeft() + (e->pos() - _dragPos)); } if (!_mousePress.testFlag(Edge::None)) { int left = _rubberband->frameGeometry().left(); int top = _rubberband->frameGeometry().top(); int right = _rubberband->frameGeometry().right(); int bottom = _rubberband->frameGeometry().bottom(); switch (_mousePress) { case Edge::Top: top = e->globalPos().y(); break; case Edge::Bottom: bottom = e->globalPos().y(); break; case Edge::Left: left = e->globalPos().x(); break; case Edge::Right: right = e->globalPos().x(); break; case Edge::TopLeft: top = e->globalPos().y(); left = e->globalPos().x(); break; case Edge::TopRight: right = e->globalPos().x(); top = e->globalPos().y(); break; case Edge::BottomLeft: bottom = e->globalPos().y(); left = e->globalPos().x(); break; case Edge::BottomRight: bottom = e->globalPos().y(); right = e->globalPos().x(); break; } QRect newRect(QPoint(left, top), QPoint(right, bottom)); if (newRect.width() < _parent->minimumWidth()) { left = _parent->frameGeometry().x(); } else if (newRect.height() < _parent->minimumHeight()) { top = _parent->frameGeometry().y(); } _parent->setGeometry(QRect(QPoint(left, top), QPoint(right, bottom))); _rubberband->setGeometry(QRect(QPoint(left, top), QPoint(right, bottom))); } } else { updateCursorShape(e->globalPos()); } } void FrameLess::updateCursorShape(const QPoint &pos) { if (_parent->isFullScreen() || _parent->isMaximized()) { if (_cursorchanged) { _parent->unsetCursor(); } return; } if (!_leftButtonPressed) { calculateCursorPosition(pos, _parent->frameGeometry(), _mouseMove); _cursorchanged = true; if (_mouseMove.testFlag(Edge::Top) || _mouseMove.testFlag(Edge::Bottom)) { _parent->setCursor(Qt::SizeVerCursor); } else if (_mouseMove.testFlag(Edge::Left) || _mouseMove.testFlag(Edge::Right)) { _parent->setCursor(Qt::SizeHorCursor); } else if (_mouseMove.testFlag(Edge::TopLeft) || _mouseMove.testFlag(Edge::BottomRight)) { _parent->setCursor(Qt::SizeFDiagCursor); } else if (_mouseMove.testFlag(Edge::TopRight) || _mouseMove.testFlag(Edge::BottomLeft)) { _parent->setCursor(Qt::SizeBDiagCursor); } else if (_cursorchanged) { _parent->unsetCursor(); _cursorchanged = false; } } } void FrameLess::calculateCursorPosition(const QPoint &pos, const QRect &framerect, Edges &_edge) { bool onLeft = pos.x() >= framerect.x() - _borderWidth && pos.x() <= framerect.x() + _borderWidth && pos.y() <= framerect.y() + framerect.height() - _borderWidth && pos.y() >= framerect.y() + _borderWidth; bool onRight = pos.x() >= framerect.x() + framerect.width() - _borderWidth && pos.x() <= framerect.x() + framerect.width() && pos.y() >= framerect.y() + _borderWidth && pos.y() <= framerect.y() + framerect.height() - _borderWidth; bool onBottom = pos.x() >= framerect.x() + _borderWidth && pos.x() <= framerect.x() + framerect.width() - _borderWidth && pos.y() >= framerect.y() + framerect.height() - _borderWidth && pos.y() <= framerect.y() + framerect.height(); bool onTop = pos.x() >= framerect.x() + _borderWidth && pos.x() <= framerect.x() + framerect.width() - _borderWidth && pos.y() >= framerect.y() && pos.y() <= framerect.y() + _borderWidth; bool onBottomLeft = pos.x() <= framerect.x() + _borderWidth && pos.x() >= framerect.x() && pos.y() <= framerect.y() + framerect.height() && pos.y() >= framerect.y() + framerect.height() - _borderWidth; bool onBottomRight = pos.x() >= framerect.x() + framerect.width() - _borderWidth && pos.x() <= framerect.x() + framerect.width() && pos.y() >= framerect.y() + framerect.height() - _borderWidth && pos.y() <= framerect.y() + framerect.height(); bool onTopRight = pos.x() >= framerect.x() + framerect.width() - _borderWidth && pos.x() <= framerect.x() + framerect.width() && pos.y() >= framerect.y() && pos.y() <= framerect.y() + _borderWidth; bool onTopLeft = pos.x() >= framerect.x() && pos.x() <= framerect.x() + _borderWidth && pos.y() >= framerect.y() && pos.y() <= framerect.y() + _borderWidth; if (onLeft) { _edge = Left; } else if (onRight) { _edge = Right; } else if (onBottom) { _edge = Bottom; } else if (onTop) { _edge = Top; } else if (onBottomLeft) { _edge = BottomLeft; } else if (onBottomRight) { _edge = BottomRight; } else if (onTopRight) { _edge = TopRight; } else if (onTopLeft) { _edge = TopLeft; } else { _edge = None; } } void FrameLess::setBorderWidth(int borderWidth) { _borderWidth = borderWidth; } int FrameLess::borderWidth() const { return _borderWidth; }
-
Good found.
Thx for reporting back.
Im sure someone will re-use this some day. -
I found very usefull this class, I added some code to handle correctly a maximized FrameLess Widget.
Invoid FrameLess::mouseMove(QMouseEvent *e) { if (_dragStart) { if (_parent->isMaximized()) { //this will handle correctly dragging when it is maximized maximizeWidth = _parent->width(); //maximizeWidth is an int attribute _parent->showNormal(); } if (_dragPos.x() > _parent->width()) _dragPos.setX((_parent->width()*_dragPos.x()) / maximizeWidth); _parent->move(e->globalX() - _dragPos.x(), e->globalY() - _dragPos.y()); } if (!_mousePress.testFlag(Edge::None) && !_parent->isMaximized() ) { //this prevent resizing if it is maximized [...]
Now I'm wondering if there is a way to implement Windows 7/10 autosize function (the function when you drag a window on top/left/right/bottom edge of desktop and it will maximize/resize to half desktop size) on a frameless widget!