Reputation: 115
I am working on a simple app for a tea timer with Python3 and PyQt5 on OSX. For aesthetic reasons I decided to remove the window frame. In order to still keep the window moveable I overloaded the mousePressEvent() and mouseMoveEvent() handlers.
However I run into a problem when the window is being moved while the mouse is over one of the buttons (QPushButton()). The cursor then jumps to the last click position and the window is moved from there. Does anyone have an idea as to why this is happening? Interestingly the same issue does not appear when clicking and moving on a QLabel() object...
If you want to try it out, here is an example code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
## ===============================================================
## IMPORTS
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
## ===============================================================
## CONSTANTS
WINDOW_WIDTH = 690
WINDOW_HEIGHT = 435
## ===============================================================
## CLASSES
class Form(QWidget):
def __init__(self, parent=None):
super().__init__()
self.windowPos = QPoint() # Maybe not necessary when button click and window movement are seperated
# Declare and specify UI elements
self.timerLabel = QLabel("00:00") # Might have to change data type here
self.timerLabel.setObjectName("timerLabel")
self.infoLabel = QLabel("No tea selected")
self.infoLabel.setObjectName("infoLabel")
self.teaOneButton = QPushButton("Tea One")
self.teaOneButton.setObjectName("teaOneButton")
self.teaTwoButton = QPushButton("Tea Two")
self.teaTwoButton.setObjectName("teaTwoButton")
# Arrange UI elements in a layout
grid = QGridLayout()
self.setLayout(grid) # Set the QGridLayout as the window's main layout
grid.setSpacing(0) # Spacing between widgets - does not work if window is resized
grid.setContentsMargins(4, 4, 4, 4)
grid.addWidget(self.timerLabel, 0, 0, 1, -1, Qt.AlignHCenter) # http://doc.qt.io/qt-5/qgridlayout.html#addWidget
grid.addWidget(self.infoLabel, 1, 0, 1, -1, Qt.AlignHCenter)
grid.addWidget(self.teaOneButton, 2, 0)
grid.addWidget(self.teaTwoButton, 2, 1)
self.resize(WINDOW_WIDTH, WINDOW_HEIGHT)
# Arranging window in center of the screen by overloading showEvent method
def showEvent(self, QShowEvent):
self.centerOnScreen()
def centerOnScreen(self):
screen = QDesktopWidget()
screenGeom = QRect(screen.screenGeometry(self))
screenCenterX = screenGeom.center().x()
screenCenterY = screenGeom.center().y()
self.move(screenCenterX - self.width() / 2,
screenCenterY - self.height() / 2)
# Overload mouseEvent handlers to make window moveable
def mousePressEvent(self, QMouseEvent):
self.windowPos = QMouseEvent.pos()
self.setCursor(QCursor(Qt.SizeAllCursor))
def mouseReleaseEvent(self, QMouseEvent):
self.setCursor(QCursor(Qt.ArrowCursor))
def mouseMoveEvent(self, QMouseEvent):
# print (self.childAt(QMouseEvent.pos()) == QLabel)
pos = QPoint(QMouseEvent.globalPos())
self.window().move(pos - self.windowPos)
## ===============================================================
## MAIN LOOP
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
screen = Form()
screen.setWindowFlags(Qt.FramelessWindowHint)
screen.show()
sys.exit(app.exec_())
Upvotes: 1
Views: 1068
Reputation: 115
Well guys I think I might have found a way to circumvent the problem.
It seemed to me, that the problem might be that QPushButtons handle mouseMoveEvents differently than, for example QLabel objects. To me it makes sense intuitively since a button object might need different mouse event handling than other unclickable objects.
So what I did was try to implement a subclass for QPushButton, overloading the mouseMoveEvent handler. At first I thought I might set the handler to ignore the event in order to delegate it up to the parent widget. That did not change anything. It actually might be something that has been going on all along. Because when I implement some sort of code into the event handling function, the button does no longer function as a source for moving the window. See for yourself:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
## ===============================================================
## IMPORTS
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
## ===============================================================
## CONSTANTS
WINDOW_WIDTH = 690
WINDOW_HEIGHT = 435
## ===============================================================
## CLASSES
class UnmoveableButton(QPushButton):
def __init__(self, text=""):
super().__init__(text)
# This event function is overloaded in order to avoid the widget from delegating the event up to the parent.
# This way, the pre-existing functionality is skipped, i.e. the window can no longer be moved while hovering over a button.
def mouseMoveEvent(self, QMouseEvent):
pass
class Form(QWidget):
def __init__(self, parent=None):
super().__init__()
self.windowPos = QPoint() # Maybe not necessary when button click and window movement are seperated
# Declare and specify UI elements
self.timerLabel = QLabel("00:00") # Might have to change data type here
self.timerLabel.setObjectName("timerLabel")
self.infoLabel = QLabel("No tea selected")
self.infoLabel.setObjectName("infoLabel")
self.teaOneButton = UnmoveableButton("Tea One")
self.teaOneButton.setObjectName("teaOneButton")
self.teaTwoButton = UnmoveableButton("Tea Two")
self.teaTwoButton.setObjectName("teaTwoButton")
# Arrange UI elements in a layout
grid = QGridLayout()
self.setLayout(grid) # Set the QGridLayout as the window's main layout
grid.setSpacing(0) # Spacing between widgets - does not work if window is resized
grid.setContentsMargins(4, 4, 4, 4)
grid.addWidget(self.timerLabel, 0, 0, 1, -1, Qt.AlignHCenter) # http://doc.qt.io/qt-5/qgridlayout.html#addWidget
grid.addWidget(self.infoLabel, 1, 0, 1, -1, Qt.AlignHCenter)
grid.addWidget(self.teaOneButton, 2, 0)
grid.addWidget(self.teaTwoButton, 2, 1)
self.resize(WINDOW_WIDTH, WINDOW_HEIGHT)
# Arranging window in center of the screen by overloading showEvent method
def showEvent(self, QShowEvent):
self.centerOnScreen()
def centerOnScreen(self):
screen = QDesktopWidget()
screenGeom = QRect(screen.screenGeometry(self))
screenCenterX = screenGeom.center().x()
screenCenterY = screenGeom.center().y()
self.move(screenCenterX - self.width() / 2,
screenCenterY - self.height() / 2)
# Overload mouseEvent handlers to make window moveable
def mousePressEvent(self, QMouseEvent):
self.windowPos = QMouseEvent.pos()
self.setCursor(QCursor(Qt.SizeAllCursor))
def mouseReleaseEvent(self, QMouseEvent):
self.setCursor(QCursor(Qt.ArrowCursor))
def mouseMoveEvent(self, QMouseEvent):
# print (self.childAt(QMouseEvent.pos()) == QLabel)
pos = QPoint(QMouseEvent.globalPos())
self.window().move(pos - self.windowPos)
## ===============================================================
## MAIN LOOP
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
screen = Form()
screen.setWindowFlags(Qt.FramelessWindowHint)
screen.show()
sys.exit(app.exec_())
I think it might not be the most elegant or correct solution for that matter, but it works fine for me right now. If someone has an idea on how to improve upon this, let me know :)
Upvotes: 1
Reputation: 355
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
Are you sure there is no namespace clash from imported members?
Upvotes: 0