PyQt5 "Projekt" Tutorial?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
FlyingThunder
User
Beiträge: 4
Registriert: Freitag 5. April 2019, 11:06

Moin, für die Basics hat mit das Tutorial von Zetcode immer gereicht, aber jetzt fehlt es mir leider an Nachschlagwerk:
Ich bastle gerade ein kleines Programm, in dem ich unter anderem ein hauptfenster habe, von dem ich eine Klasse aus einer anderen Datei aufrufen kann, die mit der QPrinter funktion und tika Internetseiten in plain-text umwandelt.

So weit, so gut - allerdings ist das bisher mein "größtes" Projekt, und fast alles, was ich so an PyQt5 Zeug geschrieben hab, hab ich quasi "rumprobiert" - sieht scheiße aus, liest sich scheiße, ist sicherlich absolut nicht so wie gedacht, aber bisher "funktioniert" es. Das reicht mir aber nicht, ich möchte sicher gehen, dass ich das auch alles verstanden habe.

Mein Hauptproblem: Ich habe absolut KEINE Ahnung, wann, wie, wo ich bei einem Projekt mit mehreren Klassen, die verschiedene PyQt Funktionen benutze, den PyQt5 Syntax benutze.
Ein Beispiel:
Wo muss ich bei einer neuen Klasse (QWidget) in die Argumente packen?
Wo muss ich am ende in die mainloop so etwas app = QtWidgets.QApplication(sys.argv) schreiben?
Wie sage ich meinem Programm, dass es alles, außer das Hauptfenster, schließen soll?
Wann packe ich einen super().__init__ call in die __init__ Funktion einer Klasse?
Warum versucht (und scheitert) meine .insertPlainText Funktion zu laufen, obwohl die Funktion die davor kam (html seite als pdf speichern per QPrinter, pdf laden per tika) noch gar nicht fertig ist? Warum asynchron?
Warum öffnet sich immer für den Bruchteil einer Sekunde ein kleines Fenster, wenn ich ein Widget aus einer anderen Klasse schließe?
Warum läuft mein Programm weiter, nachdem ich beim Hauptfenster auf x gedrückt hab?


Und warum zur Hölle geht der Qtwebengineprocess nicht von selbst weg?

Hier ist mein Projekt:
https://github.com/FlyingThunder/QtTranslator (nichts für schwache Nerven) - build sollte funktionieren, bei mir jedenfalls geht es (Bedienung dürfte selbsterklärend sein)

Danke falls jemand die Zeit und Lust opfert mir zu helfen, ich weiß echt nicht mehr wo ich noch suchen soll :(
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@FlyingThunder: Die erste Frage verstehe ich nicht. Meinst Du mit ”Argumente” was in Klammern hinter dem Klassennamen angegeben wird? Dann musst Du da `QWidget` angeben wenn Du von dieser Klasse erben willst. Das ist im Grunde keine Qt-Frage sondern Grundlage von objektorientierter Programmierung.

Auch die zweite Frage ist komisch: In die Hauptschleife kannst Du gar nichts schreiben, die ist bereits geschrieben und Teil von Qt. Die angegebene Zeile erstellt ein `QApplication`-Objekt von dem man nicht mehr als eins erstellen darf, und das bevor man irgend etwas anderes mit Qt-Objekten macht, weil das die Grundlage jeder Qt-Anwendung ist. (Also eigentlich ist das `QCoreApplication`, aber dann hat man keine GUI, sondern nur eine Konsolenanwendung.) Das steht in der Qt-Dokumentation.

Um alle Fenster ausser dem Hauptfenster zu schliessen kannst Du Dir entweder eine Liste mit allen Fenstern geben lassen (da gibt es zwei statische Methoden auf `QApplication` für) und alle ausser dem Hauptfenster schliessen. Oder Du merkst Dir in Deiner Anwendung alle Fenster die Du öffnest und die auf einen Schlag geschlossen werden sollen. Letzteres dürfte sicherer/flexibler sein.

``super().__init__(self, …)`` packst Du immer dann in die `__init__()` wenn Du die `__init__()`-Methode der Superklasse aufrufen willst. Wenn Du `super()` verwendest, willst Du das immer.

Bei `LoadHTML` scheint mir das erben von `QWidget` keinen Sinn machen, weil das gar kein Widget ist. Du verwendest rein gar nichts von `QWidget` in dieser Klasse oder wo ein Exemplar davon verwendet wird. Ausgenommen den letzten beiden Zeilen im `LoadUrlContent`-Modul – die aber so gar keinen Sinn ergeben.

Es wird nicht gewartet weil die API an der Stelle offensichlich asynchron ist und man über Signale informiert wird. Was auch gut so ist, denn sowohl das herunterladen einer Webseite als auch das Drucken nach PDF kann signifikant Zeit verbrauchen in der die GUI-Hauptschleife nicht läuft und die GUI einfriert. Es wäre auch keine schlechte Idee in der GUI irgendwie kenntlich zu machen das gerade ein Download läuft, damit der Benutzer sich auch bei einer nicht eingefrorenen GUI nicht wundert wenn scheinbar nichts passiert.

Die vorvorletzte Frage verstehe ich nicht. Beispielvorgehen?

Die vorletzte: Wird wahrscheinlich durch die letzte beantwortet.

Der Prozess verschwindet eventuell deswegen nicht weil Du ein zweites `QApplication`-Exemplar erstellst.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
FlyingThunder
User
Beiträge: 4
Registriert: Freitag 5. April 2019, 11:06

Moin,
erstmal vielen Dank - ich hab mir schon gedacht, dass viele meiner Probleme tiefer gehen als nur Qt, als kleiner Hobbyprogrammierer ist mein Lernprozess leider etwas chaotisch...
Die zweite Frage war vielleicht etwas doof formuliert, aber deine Antwort ist gut verständlich.
Das mit dem super fand ich nur so verwirrend, weil ich bei meinem rumgebastel bisher nur bei Qt so etwas gesehen habe - ich schätze, das hängt hier auch mit dem QApplication Objekt und der Vererbung zusammen, macht auch Sinn.
Das unnötige bei LoadHTML hab ich entfernt, die haben tatsächlich gar keinen Unterschied gemacht, läuft immer noch alles wie vorher :D
Das mit dem asynchronen Laufen hab ich schon befürchtet, leider hab ich bisher nichts mit async zu tun gehabt und auch mit den qt signalen noch nicht sooo viel, vielleicht finde ich irgendwo Qt spezifische dokumentation für diesen Fall... bisher ist mir da nix eingefallen, von den Standardsachen wie try, if etc. hat da nix geholfen.

Immer wenn ich in meinem Programm den FileSystem Dialog öffne (BrowseFileSystem.py) und ihn per x Knopf schließe, blinkt für einen Bruchteil einer Sekunde ein leeres Fenster auf. Nix dramatisches, aber komisch.

Das mit dem Prozess scheint nicht daran zu liegen, ich hab das zweite QApplication entfernt und sowohl in der IDE als auch im Task Manager läuft noch etwas - es hat auf jeden Fall irgendetwas mit dem LoadURLContent modul zu tun, sowohl der QTWebEngine Prozess als auch der laufende Prozess in der IDE treten immer auf, wenn ich diese Funktion benutzt habe. Kein Plan, was ich da falsch gemacht hab, vielleicht hängt es mit dem Async Problem zusammen.

Vielen vielen dank bis hierher schon, das ist mein bisher größtes Mini-Projekt und seit ich mit dem ganzen Internetseite-Laden Zeug rumbastel haperts irgendwie, weshalb ich auch an vielen Stellen aus Verzweiflung irgendeinen Schwachsinn hinzugefügt hab :P
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@FlyingThunder: Das mit dem `super()` verwende ich auch nur da wo ich es muss, weil Python's Super is nifty, but you can't use it. Ich habe bisher auch bei PyQt darauf verzichtet und bisher hat das auch funktioniert. Ich rufe da auf die ”traditionelle” Weise Methoden aus der Basisklasse auf, über die Klasse selbst. Das hat mit Vererbung zu tun, aber nichts mit `QApplication`.

Du benutzt doch Qt-Signale in Deinem Code schon. Du musst halt ”anders herum” denken – es ist nicht mehr so, das Du eine `get_irgendwas()`-Methode hast, die Du aufrufst und die solange blockiert bis das Ergebnis fertig ist und das zurück gibt, sondern Du brauchst einen Slot, also in Python einfach eine Methode, die mit dem Ergebnis aufgerufen wird. Beziehungsweise kannst Du das aus Sicht des Aufrufers blockierend machen, in dem Du einen modalen Dialog dafür programmierst. Dann hast Du auch nicht das Problem Dir Gedanken darüber machen zu müssen in welchen Zustand das Programm geraten kann weil es ja ansonsten während des herunterladens und umwandelns in eine PDF-Datei weiterhin bedienbar bleibt. In dem Dialog kann man den Benutzer auch darüber informieren was gerade passiert.

Das Fenster was da aufblinkt dürfte das `FileSystemWindow` sein, was auch irgendwie sinnlos ist. Die `show()`-Methode wird ja direkt nach dem modalen `QFileDialog` aufgerufen, und wenn man `show()` aufruft, werden Fenster/Widgets angezeigt – Überraschung ;-). Und wenn die `Main.ImportFile()`-Methode abgearbeitet ist, verschwindet das Fensterobjekt wieder, und damit blinkt das nur kurz auf, denn diese Methode ist ja kurz und schnell abgearbeitet. Ich sehe da auch nicht so wirklich den Sinn einer Klasse, also nicht nur dass es als Fenster keinen Sinn macht, sondern auch die “Methoden“ sind im Grunde nur eine handvoll Funktionen.

Das mit dem Prozess der weiterläuft könnte auch ein Bug in Qt sein: https://bugreports.qt.io/browse/QTBUG-49804
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
FlyingThunder
User
Beiträge: 4
Registriert: Freitag 5. April 2019, 11:06

Ah, das mit dem Bug macht sinn, sollte ja passen wenn mein Programm läuft und nicht crasht.. und wenn nicht schau ich nochmal ob ich nen exception handler reinbau der den prozess manuell killt...
Und ja, das mit dem slot hab ich ja schon z.T. glaube ich - LoadHTML.getPDF wird ja erst aufgerufen, wenn getWebsite fertig ist, aber die variablenabfrage in der hauptklasse, die dann letztenendes den text einfügt, läuft trotzdem direkt. ich könnte wahrscheinlich das einfüllen vom text selbst auch in die getWebsite funktion reinpacken, aber das wäre ja doof, weil die ja zur main klasse dazugehört...

denkst du ich sollte einfach zeile 105/106 auf main in eine neue funktion packen, und diese funktion dann wieder von so etwas wie self.loader.page().pdfPrintingFinished.connect(self.getPDF) aufrufen?
ich hab vorher versucht, eine schleife zu basteln, die prüft, ob temp.pdf existiert, und erst dann den inhalt liest, aber das hat nie funktioniert, weil irgendwas eingefroren ist...
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@FlyingThunder: Ich würde wie gesagt einen modalen Dialog für den asynchronen Teil anzeigen. Dann kannst Du aus Sicht des Aufrufers weiterhin eine synchrone API anbieten, und die Haupt-GUI ist auch gleich blockiert solange die asynchrone Verarbeitung läuft und man muss sich keine Gedanken darüber machen was alles passieren kann wenn der Benutzer die Haupt-GUI verwendet während eine Webseite heruntergeladen und verarbeitet wird.

Dann könntest Du Dir auch mal den Style Guide for Python Code anschauen. Durch Qt kommen da einige Sachen rein die sich daran nicht halten, weil Qt seinen eigenen Style Guide hat, aber Du hast da auch Namen die sich weder an Qt- noch an Python-Konventionen halten.

Dann hast Du eine Klasse pro Modul – wir sind hier bei Python und nicht bei Java. Es ist eine Verschwendung von Namensräumen jede Klasse noch mal in ihren eigenen Namensraum zu stecken. Mal davon abgesehen das sehr wahrscheinlich nicht alle Klassen auch tatsächlich Klassen sein müssen, noch mal: wir sind hier nicht bei Java wo man zwanghaft alles in Klassen stecken muss, könnte das wahrscheinlich alles noch problemlos in *einem* Modul stehen. Ausnahme wäre da das generierte `QtOutput`-Modul, allerdings würde man das sowieso eher nicht mehr machen, sondern die *.ui-Datei zur Laufzeit laden. In der Datei hättest Du auch nicht herumeditieren dürfen. Da war ja auch mal ein Kommentar der genau das ausgesagt hat, den Du entfernt hast.

Wenn man eine Aufteilung auf mehr als ein Modul machen möchte, dann wäre es sinnvoller das an der Trennungslinie Geschäftslogik vs. GUI zu machen, statt an den Klassen zu trennen. Die Trennung zwischen Geschäftslogik und GUI hast Du gar nicht. Wäre aber sinnvoll – auch innerhalb nur eines Moduls.

Bei mehr als einem Modul sollte man das auch alles in ein Package stecken, damit auf oberster Ebene nur *ein* Name mit allen anderen installierten Modulen und Packages konkurriert. Das verringert die Gefahr von Namenskollisionen und damit verbundenen Importproblemen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
FlyingThunder
User
Beiträge: 4
Registriert: Freitag 5. April 2019, 11:06

Ja, die Module sehen auch nur so aus, weil ich das irgendwie ganz übersichtlich fand, GUI vs Logik wurde mir auch schon geraten, bei dem Projekt hier hab ich leider 0 vorrausgeplant und deshalb ist alles "historisch gewachsen"... modaler Dialog hab ich noch nie gehört, ich schau mal was ich dazu online finde - und die .ui datei hab ich nur als modul, damit ich mir den python code angucken kann und davon noch n bisschen pyqt verstehen kann - wenn ich noch zeit hab vor der deadline (ist nur n kleines projekt für die berufsschule und für mich als FiSi nicht so wichtig) schreib ichs vlt nochmal ganz von vorne mit deinen tipps, das wird auf jeden fall lustig - hab bisher noch nie wirklich "geplant" sondern nur spontan dazugeschrieben. danke jedenfalls für alles, auch wenn meine eigentliche frage (ein ausführliches, mit beispielen begleitetes pyqt5 tutorial für größere projekte) nicht beantwortet ist, das ist schonmal die halbe miete :)
Antworten