Issue
I have a class QMovableResizableWidget
. It sub-classes another class QResizableWidget
, which is a subclass of QCustomWidget
, a subclass of QWidget
.
QMovableResizableWidget
is given no parent widget, on initialization. I've been trying to get QMovableResizableWidget().move(x,y)
to work. setGeometry
works as intended but the QMovableResizableWidget refuses to move from its original position.
However after I move, the (x,y) position of the widget changes to the position I have moved, but not the widget itself (painted on the screen). [I've tried repainting, but this fails too.]
Also, on calling move
, the WA_Moved
widget attribute is set to True, as expected.
I've searched extensively for this problem but can't find any pointers. Is there something I'm missing?
Below is a bare bones implementation of QMovableResizableWidget and snippets of it's ancestor classes:
import sys
from PySide6 import QtWidgets, QtCore, QtGui
Qt = QtCore.Qt
QMouseEvent = QtGui.QMouseEvent
QWidget = QtWidgets.QWidget
QEvent = QtCore.QEvent
CursorShape = Qt.CursorShape
QApplication = QtWidgets.QApplication
WidgetAttributes = Qt.WidgetAttribute
WindowTypes = Qt.WindowType
QPoint = QtCore.QPoint
class QCustomWidget(QWidget):
def __init__(self, p, f):
# type: (QWidget | None, WindowTypes) -> None
super().__init__(p, f)
self.setMouseTracking(True)
class QResizableWidget(QCustomWidget):
def __init__(self,p, f):
# type: (QWidget| None, WindowTypes) -> None
super().__init__(p, f)
pass
class QMovableResizableWidget(QCustomWidget):
def __init__(self,p, f):
# type: (QWidget | None, WindowTypes) -> None
super().__init__(p, f)
self.__moveInitLoc = None
self.__isDragging = False
self.setAttribute(Qt.WidgetAttribute.WA_MouseTracking, True)
def event(self, ev):
# type: (QEvent | QMouseEvent) -> bool
if isinstance(ev, QMouseEvent):
if (
ev.type() == QEvent.Type.MouseButtonPress
and ev.button() == Qt.MouseButton.LeftButton
):
# print("Movable widget clicked")
self.__isDragging = True
self.__moveInitLoc = ev.globalPos()
elif (
ev.type() == QEvent.Type.MouseMove
and self.__isDragging
):
# dragging
# print("ms move")
if self.__moveInitLoc is not None:
self.setCursor(CursorShape.DragMoveCursor)
diffLoc = ev.globalPos() - self.__moveInitLoc
newLoc = self.mapToGlobal(self.pos()) + diffLoc
self.move(newLoc) # x,y location updated in object. But object doesn't move
return True
elif ev.type() == QEvent.Type.MouseButtonRelease:
# Check if we were dragging
# print("ms Released")
self.__isDragging = False
if self.__moveInitLoc is not None:
self.__moveInitLoc = None
self.unsetCursor()
return super().event(ev)
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = QMovableResizableWidget(None, WindowTypes.Window | WindowTypes.FramelessWindowHint)
window.resize(300, 300)
window.show()
sys.exit(app.exec())
Solution
Your main issue is within this line:
newLoc = self.mapToGlobal(self.pos()) + diffLoc
You're practically translating the widget position by its own global position: mapToGlobal
maps a point in local coordinates to a point in global coordinates based on the global position of the widget.
If you call self.mapToGlobal(self.pos())
of a widget that is shown at 10, 10
(to its parent, or to the whole desktop for top level widget), the result is 20, 20
, or 10, 10
plus the screen coordinate of that widget.
Also, once the widget has been moved, any further mapping will be done based on the new position.
Here is a more appropriate implementation:
mousePos = self.mapToParent(ev.pos())
diffLoc = mousePos - self.__moveInitLoc
newLoc = self.pos() + diffLoc
self.move(newLoc)
self.__moveInitLoc = mousePos
Note that the first and last line (and their order related to the move()
call) are extremely important, because they keep track of the mapped mouse position related to the current geometry of the widget and before it was moved.
Also note that using mapToParent()
is important because it considers the possibility of the widget being a top level one: mapToGlobal()
only assumes that the position is in global coordinates, and that would be obviously wrong for children of another widget.
Update about Wayland
One of the limitations of Wayland is that it doesn't support setting a top level window geometry (position and size) from the client side. The solution, then, is to use the OS capabilities to actually move the window: get the QWindow (windowHandle()
) of the current top level widget (window()
), and simply call startSystemMove()
:
def event(self, ev):
# type: (QEvent | QMouseEvent) -> bool
if (
ev.type() == QEvent.Type.MouseButtonPress
and ev.button() == Qt.MouseButton.LeftButton
):
self.window().windowHandle().startSystemMove()
event.accept()
return True
return super().event(ev)
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.