31

I have a simple application that runs a process that can last for several minutes before completing. I am trying to provide an indication to the user that it is processing the request - such as changing the cursor to an hourglass.

But I cannot quite get it to work right. All of my attempts have resulted in either an error or had no effect. And I seem to be calling the cursor shapes incorrectly, sincePyQt4.Qt.WaitCursor returns an error that the module does not contain it.

What is the correct way to indicate to the user that the process is running?

General Grievance's user avatar
General Grievance
5,12039 gold badges40 silver badges60 bronze badges
askedNov 21, 2011 at 21:39
TimothyAWiseman's user avatar

8 Answers8

75

I thinkQApplication.setOverrideCursor is what you're looking for:

PyQt6:

from PyQt6.QtCore import Qtfrom PyQt6.QtWidgets import QApplication...QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)# do lengthy processQApplication.restoreOverrideCursor()

PyQt5:

from PyQt5.QtCore import Qtfrom PyQt5.QtWidgets import QApplication...QApplication.setOverrideCursor(Qt.WaitCursor)# do lengthy processQApplication.restoreOverrideCursor()

PyQt4:

from PyQt4.QtCore import Qtfrom PyQt4.QtGui import QApplication...QApplication.setOverrideCursor(Qt.WaitCursor)# do lengthy processQApplication.restoreOverrideCursor()
answeredNov 21, 2011 at 22:19
ekhumoro's user avatar
Sign up to request clarification or add additional context in comments.

5 Comments

Note: This works in PyQt4. However PySide 1.2.2 under Linux (Ubuntu 16.10) says "X Error: BadCursor (invalid Cursor parameter) 6" "Major opcode: 2 (X_ChangeWindowAttributes)" "Resource id: 0xa". Feeding PySide's setOverrideCursor(Qt.WaitCursor) instead of setOverrideCursor(QCursor(Qt.WaitCursor)) works -- despite the documentation saying the QCursor() is needed. Under some circumstances, there's a similar error for the restore. This appears to be a known PySide bug.
@Ubuntourist. Thanks - I can confirm the bug in PySide. TheQCursor class has a copy constructor that takes aQt.CursorShape enum value, so it is not really necessary to useQCursor itself. I suppose creating theQCursor on the Qt side is what fixes the PySide issue.
For some reason, this solution works for me in PyQt5 but it takes a focus loss to restore the regular cursor (alt+tab, or moving around other widgets that also change the cursor like any input widget).QApplication.restoreOverrideCursor() alone will not do anything if I don't move.
@Guimoute You should post a new question about this and provide aminimal reproducible example.
In PyQt6, replace Qt.WaitCursor by Qt.CursorShape.WaitCursor.
23

While Cameron's and David's answers are great for setting the wait cursor over an entire function, I find that a context manager works best for setting the wait cursor for snippets of code:

from contextlib import contextmanagerfrom PyQt4 import QtCorefrom PyQt4.QtGui import QApplication, QCursor@contextmanagerdef wait_cursor():    try:        QApplication.setOverrideCursor(QCursor(QtCore.Qt.WaitCursor))        yield    finally:        QApplication.restoreOverrideCursor()

Then put the lengthy process code in a with block:

with wait_cursor():    # do lengthy process    pass
answeredFeb 11, 2016 at 16:55
dbc's user avatar

1 Comment

This is simplest, most elegant and versatile
10

ekhumoro's solution is correct. This solution is a modification for the sake of style. I used what ekhumor's did but used a python decorator.

from PyQt4.QtCore import Qtfrom PyQt4.QtGui import QApplication, QCursor, QMainWidgetdef waiting_effects(function):    def new_function(self):        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))        try:            function(self)        except Exception as e:            raise e            print("Error {}".format(e.args[0]))        finally:            QApplication.restoreOverrideCursor()    return new_function

I can just put the decorator on any method I would like the spinner to be active on.

class MyWigdet(QMainWidget):    # ...    @waiting_effects    def doLengthyProcess(self):        # do lengthy process        pass
Sandy Chapman's user avatar
Sandy Chapman
11.3k3 gold badges61 silver badges67 bronze badges
answeredNov 30, 2013 at 2:24
Cameron White's user avatar

2 Comments

The decorator is a great idea, thanks. I'd recommend callingfunction(*args, **kwargs), returningfunction's return value, and wrapping its call in atry...finally block for even more goodness. I can't seem to put that in this comment, so next I'll try editing your solution.
Changing the try block toreturn function(*args, **kwargs) might be useful as well, or was in my case at least.
1

Better this way:

def waiting_effects(function):    def new_function(*args, **kwargs):        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))        try:            return function(*args, **kwargs)        except Exception as e:            raise e            print("Error {}".format(e.args[0]))        finally:            QApplication.restoreOverrideCursor()    return new_function
answeredNov 12, 2015 at 13:31
David Miró's user avatar

Comments

1

You can use the class QgsTemporaryCursorOverridehttps://qgis.org/api/classQgsTemporaryCursorOverride.html

But in Python, you should use the Python context manager which is now included in PyQGIS :

from qgis.PyQt.QtCore import Qtfrom qgis.utils import OverrideCursorwith OverrideCursor(Qt.WaitCursor):    do_a_slow(operation)
answeredJun 3, 2021 at 10:37
etrimaille's user avatar

Comments

1
from qtpy import QtCore, QtGui, QtWidgetsdef waiting_effect(function):    def waiting(*args, **kwargs):        QtWidgets.QApplication.setOverrideCursor(             QtCore.Qt.CursorShape(QtCore.Qt.CursorShape.WaitCursor)        )        try:            function(*args, **kwargs)             finally:            QtWidgets.QApplication.restoreOverrideCursor()

If using PyQt5/PySide2, this might be of use, works nicely with decorating functions that have multiple arguments.

answeredOct 18, 2023 at 23:55
g.stevo's user avatar

2 Comments

I like this answer. And I always appreciate alternate approaches, but this question is so old that I have finished law school and became a practicing attorney since accepting an answer. ;)
Ha congrats, if ever your clients require a nice loading animation when using a PyQt custom app you are the lawyer for them!
0

The best way to add the cursor according to me would be by using the decorators. By this way you can run any function by just adding the cursor to that function as decorator

import decorator@decorator.decoratordef showWaitCursor(func, *args, **kwargs):    QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)    try:        return func(*args, **kwargs)    finally:        QtWidgets.QApplication.restoreOverrideCursor()@showWaitCursordef youFunc():    # Long process
answeredNov 21, 2019 at 8:01
Sunil Kumar Nerella's user avatar

Comments

0

I find I often need to do:

QApplication.setOverrideCursor(Qt.WaitCursor)QApplication.ProcessEvents()...QApplication.restoreOverrideCursor()

However! During init of the GUI, I've found a modification needs to be made for some reason:

self.ui.show()  # Or your equivalent code to show the widgetQApplication.processEvents()QApplication.setOverrideCursor(Qt.WaitCursor)app.processEvents()...QApplication.restoreOverrideCursor()

Otherwise the cursor does not "restore" until the mouse passes over a widget that modifies it (like a QLineEdit), similar to what @Guimoute mentioned

answeredApr 6, 2020 at 19:52
Nat's user avatar

Comments

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.