PyQT4 Reihenfolge der Abarbeitung

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Wacholder
User
Beiträge: 8
Registriert: Dienstag 3. März 2009, 22:07

Dienstag 3. März 2009, 22:21

Hallo Zusammen,
an dieser Stelle muß ich gestehen, das ich erst mit Python anfange zu programmieren und auch von PyQT nicht viel Ahnung habe. Bisher bin ich jedoch mit meiner "Ahnungslosigkeit" ganz gut klar gekommen, jedoch habe ich folgendes Problem noch nicht lösen können:

Ich habe mir eine GUI erstellt, möchte nun in einer Funktion folgendes tun:

Code: Alles auswählen

self.ui.lineEdit_Hinweise.setText("Starte Funktion2") #Den Text eines Eingabefeldes ändern
Funktion2 #Ausführen einer anderen Funktion
self.ui.lineEdit_Hinweise.setText("Funktion2 beendet")
Das Problem ist, das das Widget wohl nicht aktualisiert wird, jedenfalls steht im Feld lineEdit_Hinweise immer gleich "Funktion2 beendet". Auch wenn ich über timer.sleep(5) statt Funktion2 aufrufe.

Ich denke, das es sich um einen typischen Anfängerfehler handelt, aber ich würde mich freuen, wenn mir jemand auf die Sprünge helfen könnte.

Danke
Gruß
Wacholder
Benutzeravatar
cofi
Moderator
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Dienstag 3. März 2009, 22:40

Versuch statt dem Python Timer mal den QTimer (singleshot).
DasIch
User
Beiträge: 2480
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Dienstag 3. März 2009, 23:13

Poste wenn lauffähigen Code, dass heisst nicht dass du unbedingt dein ganzes Program posten musst aber zumindest ein Beispiel welches dein Problem zeigt. Dann kann man sofort das Problem sehen und muss nicht anfangen irgendwie herum zu raten was den dass Problem sein könnte.

Denke einfach beim stellen der Frage daran dass hier nicht jeder unbegrenzt Zeit hat. Versuch dein Problem interessant darzustellen und so dass sich die nötige Zeit die man dafür braucht abschätzen lässt. Ein interessantes Problem zu lösen ist interessanter als die nte simple Frage zu beantworten oder jemandem die Informationen aus der Nase zu ziehen.
jerch
User
Beiträge: 1630
Registriert: Mittwoch 4. März 2009, 14:19

Mittwoch 4. März 2009, 14:35

@Wacholder
So wird das leider nix, damit PyQt die Änderungen vornehmen kann, muß zwischendurch der event-loop von PyQt zum Zuge kommen. Egal in welchem Kontext Du Deinen Code aufrufst, er dürfte mit einem sleep() dazwischen max. sleep()-lange warten und dann "Funktion2 beendet" anzeigen.

Lösung:
Damit Dein gewünschtes Verhalten funktioniert, muß Du auf events zurückgreifen. Um das zu ermöglichen, solltest in Gui-Programmierung auf timer-events setzen, um die Gui responsiv zu halten, im PyQt-Falle wäre das mittels eines QTimers realisierbar. (Das gilt für den Fall, daß Deine Funktion2() lange läuft). Auch könnte man das Ganze threaded realisieren, der Aufwand (hohe Fehlerträchtigkeit und Seiteneffekte!) lohnt aber nur für sehr umfangreiche Kalkulationen.

Wenn Du Dein Problem etwas genauer schilderst, kann Dir vllt. der richtige Weg aufgezeigt werden.

Grüße, Jerch
Wacholder
User
Beiträge: 8
Registriert: Dienstag 3. März 2009, 22:07

Mittwoch 4. März 2009, 15:26

Hallo Zusammen,

ich merke, das ich hier wohl noch einwenig dazulernen muß. :roll:

Ich werde mir den QTimer mal genauer ansehen.

Ich bräuchte eigentlich nur eine Möglichkeit, dem User des Programmes anzuzeigen, das im Hintergrund eine Aktion ausgeführt wird.
jerch
User
Beiträge: 1630
Registriert: Mittwoch 4. März 2009, 14:19

Mittwoch 4. März 2009, 15:44

Das ist nicht ohne weiteres möglich, da in Deinem Falle nichts im Hintergrund gemacht wird, sondern den Gui-MainThread blockiert. Deshalb meldet sich die Gui auch erst mit Deinem "Beendet" zurück.

klassischer Timer-Ansatz:
Starte eine Timer-Instanz mittels einer Aktion, z.B. Buttonklick. Der Timer startet hierauf Deine gewünschte Funktion. Sollte die Funktion zu lange brauchen um fertig zu werden (z.B. jenseits von 300ms sein), muß Du die Funktion "zerteilen", d.h. dafür Sorge tragen, daß die Funktion nur einen Teil abarbeitet. Eventl. Zwischenergebnisse muß Du natürlich mitführen. Wenn die Funktion nun sich beendet, wird der Timer angehalten, und nach einem Zeitintervall wiederaufgerufen. Die Funktion arbeitet dann weiter, wo sie zuletzt aufgehört hat. Zwischenzeitlich hat so PyQt die Möglichkeit, die Gui-events abzuarbeiten, z.B. Deine Labeländerung. Am Ende solltest Du den Timer dann deaktivieren (sonst läuft er ewig weiter).
QTimer ist hierfür predestiniert, Du kannst ihm ein repeat-Intervall bei der Instanzierung übergeben.
Solltest Du ein Code-Bsp. so kann ich ein Schnippet rauskramen, ist allerdings in c++.

Grüße, Jörg
Wacholder
User
Beiträge: 8
Registriert: Dienstag 3. März 2009, 22:07

Mittwoch 4. März 2009, 17:50

Hallo Jörg,
danke für Deine Ausführungen, die Methodik habe ich soweit verstanden, aber ich denke soweit bin ich noch nicht.

Für meine erste Version des Programmes habe mich dazu entschlossen, die klassische Sanduhr als Rückmeldung zu benutzen.

Code: Alles auswählen

app.setOverrideCursor(QtCore.Qt.WaitCursor)   
Funktion2
app.restoreOverrideCursor()
scheint ja zu funktionieren.

Auch wenn ich C++ nicht programmieren kann, wäre es schön, wenn Du den "Schnipsel" noch anfügen könntest. Ggf kann ich in einer Nachfolgeversion meines Progammes das noch gebrauchen. :wink:
jerch
User
Beiträge: 1630
Registriert: Mittwoch 4. März 2009, 14:19

Donnerstag 5. März 2009, 01:49

Ich habe hier mal ein Bsp. auf die Schnelle erstellt, was zusätzlich gleich noch eine Kuriosität von PyQt (oder Qt im Allgemeinen) aufdeckt. Doch dazu später mehr.

Der Code sollte lauffähig sein (Ich bitte um Nachsicht bei groben Schnitzern, habs in 10min zusammengeschrieben). Probiers einfach mal aus, mittels "Start - ohne Timer" solltest Du das Verhalten Deiner Lösung provozieren können (alles friert ein), wobei die while-Schleife die Funktionsarbeit darstellen soll.
Die Timerversion führt nur diskrete Schritte der Funktion aus und die Gui bleibt responsiv. Angehalten wird der Timer mit Beendigung der "Funktion", hier das Erreichen von 100, oder mittels Stop-Button.

Doch jetzt zu der Kuriosität:
Wenn man Zeile 44 hinzunimmt, dann funktionieren plötzlich die draw-events der Widgets. Es scheint, als ob QProgressBar.setValue() selbst irgendwie "getimert" ist. Mouse-events funktionieren trotzdem nach wie vor nicht. :K

Grüße, Jerch

hier gibts frischen Code
Zuletzt geändert von jerch am Donnerstag 5. März 2009, 12:37, insgesamt 1-mal geändert.
lunar

Donnerstag 5. März 2009, 09:03

Die Kuriosität ist einfach zu erklären. Normalerweise rufen Widgets .update() auf, wenn sie neu gezeichnet werden müssen (was bei einer Veränderung des Status-Wert eines Fortschrittsbalkens ja der Fall ist). .update() löst ein Ereignis aus, welches durch die Ereignisschleife behandelt wird. So kann Qt besser optimieren, z.B. um unnötiges Neuzeichnen zu verhindern (ein Client-Widget muss nicht gesondert neu gezeichnet werden, wenn gleich darauf das Hauptfenster komplett neu gezeichnet werden würde).

QProgressBar aber ruft wohl .repaint() auf, was das Widget sofort neu zeichnet.

Ich persönlich würde einen ja einfach einen QThread einsetzen. Das ist auch nicht komplexer als die Lösung über einen Timer ...
jerch
User
Beiträge: 1630
Registriert: Mittwoch 4. März 2009, 14:19

Donnerstag 5. März 2009, 12:30

Danke lunar, die Sache mit update() und repaint() klingt einleuchtend, ein ProgressBar sollte halt synchron sein.

Mit QThread hat Du natürlich Recht, nur wollte ich jmd., der gerade mit Python/PyQt anfängt, nicht die "Gehirnschmalzkomplexität" rund um threading/QThread zumuten. :wink:
jerch
User
Beiträge: 1630
Registriert: Mittwoch 4. März 2009, 14:19

Donnerstag 5. März 2009, 16:14

so, nun mit threadbutton --> Code

Wollte das nur der Vollständigkeit halber nachschieben.

@wacholder
Die Threadlösung möchte ich Dir trotzdem nicht empfehlen, es sei denn, Du bist der Master of all known thread desaster von einer anderen Programmiersprache her. Vom Deadlock bis zum zum schweren Ausnahmefehler ist leider alles drin mit Threadprogrammierung, ohne das der Fehler gleich offensichtlich sein muß. Oh Gott, wir werden alle sterben!
DasIch
User
Beiträge: 2480
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Donnerstag 5. März 2009, 21:22

Code: Alles auswählen

with obj.lock:
    # do something
Schon tritt kein deadlock mehr auf, ich weiß die Lösung ist unglaublich kompliziert aber wenn man sich ganz doll konzentriert kann man das Problem noch so gerade eben verhindern. Mal ehrlich Threads in Python sind wirklich trivial.
Wacholder
User
Beiträge: 8
Registriert: Dienstag 3. März 2009, 22:07

Donnerstag 5. März 2009, 22:15

Genial !

Auf dem ersten Blick bevorzuge ich den Lösungsweg über den Timer. Er ist mir verständlicher.
Das liegt aber daran, das ich die Funktionalität von QThread (noch) nicht beherrsche.

Ich sehe, das ich noch eine Menge lernen muß. Aber ich finde es erstaunlich, wie weit man auch mit sehr geringen Programmierkenntnissen mittels Python Programme auf die Beine stellen kann. Habe nur geringe Kenntnisse TurboPascal (zuletzt vor ca 15 Jahren genutzt) und VBA.

Nochmals vielen Dank, Ihr habt mir sehr weitergeholfen.

Gruß
Wacholder.
lunar

Freitag 6. März 2009, 10:30

DasIch hat geschrieben:

Code: Alles auswählen

with obj.lock:
    # do something
Schon tritt kein deadlock mehr auf, ich weiß die Lösung ist unglaublich kompliziert aber wenn man sich ganz doll konzentriert kann man das Problem noch so gerade eben verhindern. Mal ehrlich Threads in Python sind wirklich trivial.
Diesen überflüssigen Kommentar hättest du dir sparen können. Zum einen geht es hier um Qt-Threads, nicht um Python-Threads, zum anderen ist es schon ziemlich naiv, im Locking die Lösung aller Threading-Probleme zu sehen. Man kann auch mit Locks schöne Deadlocks produzieren ...
Antworten