Status Zeile mit QtTextEdit?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Tinker232
User
Beiträge: 50
Registriert: Mittwoch 25. Juli 2018, 13:45

Hallo zusammen,

für das GUI meines Datenloggers möchte ich gerne eine Status Box erstellen in dem man bestimmte Parameter überwachen kann, die durch die Funktionen definiert sind und dort auch geschrieben werden, sofern sie aufgerufen werden. Alternativ wäre auch ein Output der Einträge auf der Konsole dort in Ordnung.
Ich dachte mir es funktioniert mit einem QtTextEdit? Wenn ja, wie bekomme ich dort Zeilenweise meine Einträge hinein? Das Fenster hat eine bestimmte Größe und wenn mehr vorhanden ist als dargestellt werden kann soll man hoch und runterscrollen können (wie ein Logbuch eben).
Wenn nein, welche Alternative hat man?

Vielen Dank und LG
Tina
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das sollte mit einem QTextEdit gehen, ja. Den kannst du auf read-only stellen. Du musst allerdings noch eine Funktionalitaet einbauen, die automatisch immer ans Ende scrollt, dann aber merkt, wenn der Benutzer per Scrollbar nach oben gescrollt hat, und das *nicht* mehr tun. Oder du machst das mit einem extra Toggle-Button.

Und je nach Laenge solltest du irgendwann auch von vorne wieder Zeilen rauswerfen, damit dein Buffer im Speicher nicht endlos waechst.
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Alternative wäre `QPlainTextEdit` wenn Du die Formatierungsmöglichkeiten in einem `QTextEdit` nicht wirklich brauchst. Ansonsten haben die einen Textcursor, den kann man abfragen, und über den lässt sich dann auch programmatisch Text einfügen.

Bei beiden steht in der Dokumentation auch noch ein guter Tipp wie man die Mende des angezeigten Textes begrenzen kann, wenn man beispielsweise Logdaten anzeigen möchte die sehr lang werden können, wo man alte Daten dann wieder verschwinden lassen möchte.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Tinker232
User
Beiträge: 50
Registriert: Mittwoch 25. Juli 2018, 13:45

Danke. Ich habe das mal mit QPlainTextEdit probiert. Allerdings möchte ich aus verschiedenen Funktionen heraus Texte in das Log hinzufügen und dabei gibt es Fehler wie:
@QObject::connect: Cannot queue arguments of type QTextBlock
@QObject::connect: Make sure QTextBlock is registered using qRegisterMetaType()

Sobald ich innerhalb einer Funktion mit self.tb_log.appendPlainText("Text") etwas hinzufügen macht er es problemlos...

Alternativ wie kann ich einfach den Text der Konsole immer in diesem Fenster ausgeben?
VG Tina
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tinker232: Ohne Code zu sehen kann man schlecht sagen was da schief läuft.

Um Text der sonst auf der Standardausgabe und/oder der Standardfehlerausgabe landen würde, müsste man `sys.stdout` und `sys.stderr` durch eigene Objekte ersetzen, die über die dort erwartete API verfügen und die Ausgaben dann in das `QPlainTextEdit` umleiten.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Tinker232
User
Beiträge: 50
Registriert: Mittwoch 25. Juli 2018, 13:45

Anbei der Code:

Ich habe einmal eine GUI.py und eine Func.py.
Die GUI wird in der Func importiert und dann als GUI aufgerufen. Beim drücken des Aufzeichnungsbuttons wird ein neuer Thread erzeugt der für die Dateerzeigung und das Schreiben zuständig ist, sodass das GUI bedienbar bleibt.


func_py
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der Thread ist das Problem hier. Damit man mit Threads arbeiten kann, muss man sogenannte Queued Connections benutzen. Das tust du denke ich auch, sehen kann ich das nicht. Denn die fileupload-Seite deiner Wahl schubst mir nur Disk-Image-Files auf meinen Rechner, wenn ich da was runter lade. Und die traue ich mich nicht zu oeffnen. Pastebin ist ggf. besser geeignet.

Wie dem auch sei. Queued Connections funktionieren nur mit Datentypen als Argumenten, die bestimmte Anforderungen erfuellen. Und mit der Qt Laufzeitumgebung registriert sind. Da geht es um Lebenszyklus und so. Und augenscheinlich ist der von dir gewaehlte Datentyp QTextBlock das nicht.

Ich wuerde vermuten du kannst das Problem loesen, indem du einen simpleren Typen wie einen Python String oder aehnliches benutzt. Der sollte marshalbar sein.
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tinker232: Ein paar Anmerkungen zum Quelltext:

Das Hauptprogramm sollte nicht auf Modulebene, sondern in einer Funktion stehen. Die heisst üblicherweise `main()`. Wenn man das macht, dann fällt das Programm auf die Nase weil in `RecordThread` an einigen Stellen einfach so auf das vorher globale `gui` zugreifen. Funktionen und Methoden sollten so etwas nicht machen, sondern alles was sie ausser Konstanten benötigen, sollte über Argumente übergeben werden. Sonst wird ein Programm schnell unübersichtlich und man bekommt Probleme mit den Abhängigkeiten. Du könntest im Moment zum Beispiel `RecordThread` nicht problemlos in ein anderes Modul verschieben wegen der Abhängigkeit von `gui` auf Modulebene.

Feuchtigkeit und Temperatur werden nur einmal *vor* der Schleife ermittelt und dann werden immer diese selben Werte in die Datei geschrieben. Das ist so sicher nicht gewollt.
`sys.exit()` sollte man nur im Hauptprogramm aufrufen und nicht in irgendwelchen GUI-Methoden. Das ist eine sehr rabiate Methode und man nimmt damit sich selbst und möglicherweise auch dem GUI-Rahmenwerk die Möglichkeit noch Aufräumarbeiten durchzuführen am Programmende. Die Qt-Hauptschleife (`QApplication.exec_()`) wird normalerweise verlassen sobald alle Fenster geschlossen wurden. Es reicht also aus, wenn Du in der `closeApplication()`-Methode das Fenster schliesst.

An `filename` wird ein Wert gebunden der noch gar nicht der Dateiname ist – das ist verwirrend. Zudem enthält Dein Dateiname am Ende zweimal Datum und Uhrzeit, einmal lokal und einmal UTC. Das ist doch sicher nicht so gewollt‽ Dabei enthält `utc` *nicht* die UTC-Zeitangabe sondern die Lokale!

'a' ist hier ziemlich wahrscheinlich der falsche Dateimodus.

Bei Dateinamen ist es zudem günstiger das Datum in der Reihenfolge Jahr, Monat, und Tag zu verwenden, weil die lexikografische Sortierung dann mit der Zeitlichen übereinstimmt.

Die Importe könnte man mal aufräumen. Üblich ist ein Modul pro Import, und gruppiert nach Standardbibliothek, externe Bibliotheken, und dann solche die zum eigenen Projekt gehören.

Das ``from PyQt5 import …`` wird nicht gebraucht. Du verwendest daraus nur `QtCore` um auf `QTimer` zuzugreifen, aber `QTimer` wird auch noch mal extra direkt importiert.

Aus `QtCore` werden `Qt` und `QThread` importiert, aber nicht verwendet. Ebenfalls importiert aber nicht verwendet wird `RPi.GPIO`.

Die 1 bei `GUI1` sollte da nicht stehen und Modulnamen werden per Konvention klein geschrieben.

Viele Zeilen sind zu lang. Vor allem wegen der Kommentare die meistens genau da anfangen wo traditionell eigentlich die Grenze für die Zeilenlänge ist: bei 80 Zeichen. Das sieht in Situationen wo nach 80 Zeichen umgebrochen wird, dann sehr unübersichtlich aus. Wenn die Zeilen zu lang werden ist es üblich den Kommentar *vor* den kommentierten Code zu schreiben, und nicht daneben.

Ein Grossteil der Kommentare ist auch überflüssig. Faustregel: Ein Kommentar soll nicht beschreiben *was* gemacht wird, denn das steht da ja bereits als Code, sondern *warum* es *so* gemacht wird, sofern das nicht offensichtlich ist.

Code wie ``self.__timer.start()`` mit ``# start the timer`` zu kommentieren bringt dem Leser absolut keinen Mehrwert.

Ausrichten von an einer bestimmten Spalte (abgesehen von der initialen Einrückung der Zeile) macht nur unnötig Arbeit, weil man jedes mal wenn man den Code ändert, auch die Ausrichtung wieder geraderücken muss.

In der `__init__()` sollten alle Attribute definiert werden, oder zumindest nachdem sie durchlaufen wurde. In irgendwelchen anderen Methoden neue Attribute einzuführen ist unübersichtlich und fehleranfällig. `recThread` sollte also schon in der `__init__` definiert werden. Wenn es dafür noch keinen Wert gibt, dann mit `None`. Bei `period` macht es keinen Sinn das als Attribut auf dem GUI-Objekt anzulegen.

Der Attributname `status` ist ziemlich allgemein. Man muss erst den Code anschauen der das Benutzt um zu verstehen was `True`/`False` hier bedeuten. Bei `is_recording` wüsste man auch schon wenn man die Initialisierung sieht, was der Name bedeutet.

Das `utc`-Attribut wird nirgends verwendet, kann also weg. Und `utcTime()` hiesse besser `updateTime()` oder so ähnlich. Das `utc` stimmt ja auch gar nicht! Den Rückgabewert braucht man dann auch nicht mehr, denn der wird ja nirgends verwendet. Du wandelst dort, und noch an drei anderen Stellen im Code eine Zeichenkette mit `str()` in eine Zeichenkette um. Was keinen Sinn macht.

Doppelte führende Unterstriche bei Attributen sind dazu da um Namenskollisionen bei Mixin-Klassen oder sehr tiefer Vererbung zu vermeiden. Beides sehe ich bei `__timer` nicht. Attribute die nicht teil der öffentlichen API sind, werden mit *einem* führenden Unterstrich gekennzeichnet. Wobei das Objekt soweit ich das sehe gar kein Attribut sein muss, denn es wird ja nirgends verwendet.

Warum wird das Argument bei `setStatus()` als `stat` abgekürzt und heisst nicht `status`? Oder auch hier besser `is_recording`, dann weiss man auch hier gleich was der Status bedeutet und das ein Wahrheitswert erwartet wird. Beim Methodennamen gilt das gleiche wie auch schon beim `status`-Attribut: der Name könnte mehr Information enthalten.

In der Methode wird in beiden Zweigen das Attribut auf den gleichen Wert gesetzt wie das Argument. Das kann man also auch *einmal* vor dem ``if``/``else`` machen.

Mit literalen Wahrheitswerten zu vergleichen ist redundant. Da kommt ja wieder ein Wahrheitswert heraus, entweder den den man sowieso schon hatte, oder das Gegenteil. Im letzten Fall kann man dann ``not`` verwenden.

Wie kommen die drei ``else: pass`` zustande? Das ist sinnloser Code → weg damit.

Das sowohl das Modul `time` als auch `datetime` verwendet werden um die aktuelle (UTC)-Zeit in Zeichenketten umzuwandeln ist inkonsistent. Ich würde dafür ausschliesslich `datetime` verwenden.

Das aus `RecordThread.run()` direkt an GUI-Elementen verändert wird, ist zu eng gekoppelt. `RecordThread` sollte nichts über die Interna der GUI wissen müssen. Zudem wird hier GUI und Anwendungslogik vermischt. Das würde man eher trennen wollen, so das man die Aufzeichnung auch ohne GUI machen könnte.

Ungetesteter Zwischenstand:

(Auf Wunsch des TE entfernt)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten