Make a frameless QWidget resizable with QRubberBand ?
-
@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!