Seite 1 von 1

Benutzereingaben deaktivieren

Verfasst: 14. Oktober 2010 12:09
von ws
Hallo,

ich habe folgendes Problem. Ich habe eine PyQt-Anwendung, die in bestimmten Situationen einen Thread benutzt, um eine Operation einer externen Applikation zu kommunizieren. Für die Dauer dieser Operation soll die aufrufende Applikation blockiert werden. Das ganze soll deshalb in einem Thread laufen, damit das GUI der aufrufenden Applikation sichtbar bleibt (unter XP wird sonst etwa der Fensterinhalt weiss), d.h. Paint-Events sollen weiterhin verarbeitet werden, Usereingaben dagegen in der Regel nicht. Ich habe also folgenden Code geschrieben

Code: Alles auswählen

        wt = WorkerThread(func, *args, **kwArgs)
        wt.start()

        timeOutTime = time.time() + self._timeOut if self._timeOut else 0
        while not wt._finished:
            QtGui.qApp.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents)
            time.sleep(self._sleepInterval)
            if timeOutTime:
                if time.time() > timeOutTime:
                    break

        if wt._finished:
            ret = wt._ret
        else:
            ret = ThreadExecutionError
Das tut jetzt auch weitgehend das, was ich will, mit einer Einschränkung:
Der Aufruf von

Code: Alles auswählen

QtGui.qApp.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) 
sorgt zwar dafür, dass während der Laufzeit des Threads die UserInputEvents nicht verarbeitet werden, diese werden aber nicht gelöscht, sondern nach Beendigung der Routine beim nächsten Aufruf von processEvents() ohne Parameter verarbeitet, wie es ja auch dokumentiert ist:

Do not process user input events, such as ButtonPress and KeyPress. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.

Ich möchte aber, dass die Benutzereingaben, die der Anwender macht, während der Thread läuft, einfach gelöscht werden. Was ist der richtige Weg dafür? Muss ich auf der QEventLoop für die Dauer des Threads einen Eventfilter installieren, den ich danach wieder entferne?

Gruss

Wolfgang

Verfasst: 14. Oktober 2010 12:17
von solarix
Es ist immer gut wenn eine Software für den User nachvollziehbar arbeitet. Einfach die Usereingaben zu sperren fände ich als User nicht so toll..

An deiner Stelle würde ich ein QSplashSceen mit einer entsprechenden Meldung anzeigen. Der sperrt dann auch die User-Eingaben.

Ausprobieren könntest du auch, ob eine Änderung des Mauscursors den gewünschten Effekt bringt (ich weiss gerade nicht ob das nur eine optische Änderung ist oder ob dann auch die Input-Events ignoriert werden):
http://doc.qt.nokia.com/4.7/qapplicatio ... rideCursor

hth..

Splashscreen

Verfasst: 14. Oktober 2010 12:34
von ws
Der Grund, warum überhaupt die Paint-Events verarbeitet werden sollen (sonst könnte ich ja einfach das processEvents weglassen), ist, dass die Anwender teilweise in die aufrufende Applikation zurückwechseln wollen, um dort Informationen im GUI sehen zu können, deswegen hilft mir ein Splash-Screen nicht wirklich. Was ich brauche, ist die Möglichkeit, User-Eingaben zu ignorieren, also wegzuwerfen, und nicht später auszuführen.

Natürlich sollte dem Anwender irgendwie angezeigt werden, dass auf die Beendigung der entsprechenden Applikation gewartet wird, evtl. in einem
Dialog oder über eine Art Heartbeat-Anzeige.

Gruss

Wolfgang

Verfasst: 14. Oktober 2010 13:33
von franzf
Einfach alle nötigen Widgets disablen (setEnabled(false);). Children werden auch auf disabled gesetzt. Damit können keine Buttons mehr geklickt werden, aber auch kein LineEdit mehr verändert werden! Hast du ein HauptWidget, in dem alle zu disablenden Widgets als Child liegen, hast du mit einem "MainWidget->setEnabled(false);" alles was du brauchst!

Soll es weniger intrusiv sein (Widgets werden ausgegraut), kannst du widgetweise Flags setzen. QLineEdit, QTextEdit, ... haben z.B. ein "readOnly"-Property. ItemView kannst du mit "QAbstractItemView::NoEditTriggers" auf nicht editierbar stellen. usw.

//
Aber - ich muss sagen - dein Thread-Einsatz ist Müll :/ Du startest einen Thread, wartest aber im HauptThread auf dessen Beendigung. Da kannst gleich auf Threads und den damit produzierten Overhead verzichten! Dein Ansatz blockiert die Gui nämlich genauso, verhindert wird das nur durch das processEvents, was ohne zusätzlichem Thread genauso funktioniert.

Schau dir mal QThread an, und die Komunikationsmechanismen, die der bietet (SIGNAL/SLOT, event-Queue, usw). Gibt hier im Forum einige interessante Beiträge zum Thema Threads.

Verfasst: 15. Oktober 2010 11:23
von ws
@franzf:Die Art, wie ich das hier mache, ist PyQt geschuldet. Python verhält sich in Bezug auf Threads etwas eigentümlich. Das ursprüngliche Verhalten (ohne Threads) war erstmal, dass das aufrufende GUI weiss wurde, d.h. der Inhalt nicht mehr sichtbar war. Der Grund war, dass Python die entsprechende (C++-)Routine blockierend ausführte, d.h. während der Laufzeit der Operation war der GUI-Thread tot. Das liegt daran, dass es in Python den sogenannten Global Interpreter Lock gibt, und das Multithreading in Python etwas eingeschränkt ist. Kurz gesagt, unter PyQt ist es tatsächlich nötig, entsprechende C++-Routinen in einem Thread aufzurufen, wenn der GUI-Thread nicht einfrieren soll (Ausserdem muss man seine C++-Routinen mit ein paar Makros erweitern).

Das Disablen der Widgets, das Du vorschlägst, scheint mir die beste Lösung zu sein. Wenn die Widgets ausgegraut sind, ist das kein Nachteil, dann sieht der Anwender zumindest schon mal, dass das GUI zur Zeit nicht benutzt werden kann, weil eine andere Operation läuft.

Vielen Dank

Wolfgang