Michael
Michael

Reputation: 115

Moving PyQt5 App - QPushButton issue

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

Answers (2)

Michael
Michael

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

grug.0
grug.0

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

Related Questions