Search code examples
pythonpython-3.xpyqtpyqt5qmessagebox

Change cursor for QMessageBox and SaveFileDialog


In PyQt5 I can change the cursor for an object using:

    Object.setCursor(QCursor(Qt.PointingHandCursor))

For the other buttons I use this class but it does not change the cursors in QmessageBox or Qfiledialog:

class QPushButton(QPushButton):
    def __init__(self, parent=None):
        super(QPushButton, self).__init__(parent)
        self.setCursor(QCursor(Qt.PointingHandCursor))

How can I change the cursor for ALL buttons in QMessageBox and QFileDialog?

Example of a Messagebox method

def onNotConnected(self):
        err = QMessageBox.question(
            self, DONGLE_NOT_CONN, DONGLE_NOT_CONN_MSG, QMessageBox.Ok | QMessageBox.Cancel)
        if err == QMessageBox.Ok:            
            self.updating_thread(self.device_code)
        else:
            self.restart_program()

Solution

  • QMessageBox and QFileDialog have the setCursor() method since they inherit from QWidget. But the problem in your case is with static methods since you can not access the object directly.

    So the solution is to take advantage of a particular characteristic of these static methods: they are topLevels, so we can filter it using QApplication.topLevelWidgets(), but the other problem is that they are blocking so nothing will be executed synchronously, so The trick is to use QTimer.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    def onTimeout():
        for w in QtWidgets.QApplication.topLevelWidgets():
            if isinstance(w, QtWidgets.QMessageBox):
                for button in w.buttons():
                    button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent=None)
            self.show()
    
            QtCore.QTimer.singleShot(0, onTimeout)
            res = QtWidgets.QMessageBox.question(self, 
                "title", 
                "text",  
                QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
    
    
    if __name__ == '__main__':
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    

    Also in your sample that we can filter the filter using the parent of the QMessageBox and possibly the QFileDialog is the window.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent=None)
            self.show()
    
            QtCore.QTimer.singleShot(0, self.onTimeout)
            msgBox = QtWidgets.QMessageBox.question(self, 
                "title", 
                "text",  
                QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
    
            QtCore.QTimer.singleShot(0, self.onTimeout)
            fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, 
                "Save File",
                QtCore.QDir.homePath(),
                "Images (*.png *.xpm *.jpg)",
                "",
                QtWidgets.QFileDialog.DontUseNativeDialog)
    
        def onTimeout(self):
            for w in QtWidgets.QApplication.topLevelWidgets():
                if isinstance(w, QtWidgets.QMessageBox) and w.parent() == self:
                    for button in w.buttons():
                        button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
                elif isinstance(w, QtWidgets.QFileDialog) and w.parent() == self:
                    for button in w.findChildren(QtWidgets.QPushButton):
                        button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
    
    
    
    if __name__ == '__main__':
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())