Search code examples
pythonpyqt5qwidgetqpushbutton

Pyqt5 draggable QPushButton


I have this sample code on how to drag and move a QPushButton. The only issue of that code is, when you drag the button and release it, the button status is stay as checked.

Can someone help me to change the code so that, after drag the button and release the button status is automatically unchecked. So, I don't have to click it to uncheck it.

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtCore import Qt

class DragButton(QPushButton):

    def mousePressEvent(self, event):
        self.__mousePressPos = None
        self.__mouseMovePos = None
        if event.button() == Qt.LeftButton:
            self.__mousePressPos = event.globalPos()
            self.__mouseMovePos = event.globalPos()

        super(DragButton, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            # adjust offset from clicked point to origin of widget
            currPos = self.mapToGlobal(self.pos())
            globalPos = event.globalPos()
            diff = globalPos - self.__mouseMovePos
            newPos = self.mapFromGlobal(currPos + diff)
            self.move(newPos)

            self.__mouseMovePos = globalPos

        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self.__mousePressPos is not None:
            moved = event.globalPos() - self.__mousePressPos 
            if moved.manhattanLength() > 3:
                event.ignore()
                return

        super(DragButton, self).mouseReleaseEvent(event)

def clicked():
    print ("click as normal!")

if __name__ == "__main__":
    app = QApplication([])
    w   = QWidget()
    w.resize(800,600)

    button = DragButton("Drag", w)
    button.clicked.connect(clicked)

    w.show()
    app.exec_() 

Solution

  • You return in the mouseReleaseEvent, which means that you're not letting the button know that it actually received a mouse release, thus leaving the status as pressed.

    def mouseReleaseEvent(self, event):
        if self.__mousePressPos is not None:
            moved = event.globalPos() - self.__mousePressPos 
            if moved.manhattanLength() > 3:
                event.ignore()
                return # <-- the problem is here!
    
        super(DragButton, self).mouseReleaseEvent(event)
    

    You can see that it behaves correctly if you move the mouse by just a few pixels (below the Manhattan length), so you'll have to completely remove that if block, or call self.setDown(False) before returning if you want to avoid sending the clicked signal.