QPlainTextEdit hängt

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

DaMutz hat geschrieben: Deine READTXT-Klasse (nicht PEP8 konform) muss ein Signal senden sobald eine Linie gelesen wurde.
Genau das wurde ja auch so bei den von mir verlinkten Beispielen demonstriert :!:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

@DaMutz danke erstmal für deine Mühe, ich glaube ich muss mir mal ein ordentliches Tutorial fürs arbeiten mit Qt suchen. Hast du da was zur Hand? Ansich verstehe ich was du meinst bin mir aber mit den Signalen und Slots ein wenig unsicher...

@DaMutz und Hyperion :oops: Habe mir soeben die PEP8 Richtlinien durchgelesen und versuche mich beim nächsten mal dran zuhalten ;)
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

CODE

So ich habe mal ein wenig auf und umgeräumt... Leider habe ich es nicht zum laufen gebracht.

Wieso wird das Signal anscheinend nicht gesendet?
Und wenn ich Zeile 29:

Code: Alles auswählen

read.newLineRead.connect(self.test)
durch

Code: Alles auswählen

read.newLineRead.connect(textEdit.appendPlainText)
ersetze wird dann der text von Zeile 50 automatisch als Eingabevariable gesetzt?


Btw wie bekomme ich Zeile 19 bis 22 auf 80 Zeichen getrimmt? ^^
Benutzeravatar
DaMutz
User
Beiträge: 202
Registriert: Freitag 31. Oktober 2008, 17:25

Sieht schon viel besser aus :-)
JonasR hat geschrieben:Wieso wird das Signal anscheinend nicht gesendet?
Das Signal wird schon gesendet, nur machst du kein connect, bzw. erzeugst ein neues ReadTXT Objekt bei dem du das Signal nicht abfängst, und das korrekt verbundene startest du nicht. Binde doch den 'clicked' Event direkt an 'read.start', dann benötigst du auch die 'startWork' Methode nicht...
JonasR hat geschrieben: Und wenn ich Zeile 29:

Code: Alles auswählen

read.newLineRead.connect(self.test)
durch

Code: Alles auswählen

read.newLineRead.connect(textEdit.appendPlainText)
ersetze wird dann der text von Zeile 50 automatisch als Eingabevariable gesetzt?
ja. Deine Testfunktion benötigt noch einen zusätzlichen Parameter für den Text.
JonasR hat geschrieben: Btw wie bekomme ich Zeile 19 bis 22 auf 80 Zeichen getrimmt? ^^
das weiss ich auch nicht. Ich denke es gibt 2 Möglichkeiten. Entweder du benutzt eine Hilfsvariable für den setText Teil oder du formatierst es so:

Code: Alles auswählen

self.pushButton.setText(
            QtGui.QApplication.translate(
                "Form",
                "Start",
                None,
                QtGui.QApplication.UnicodeUTF8
            )
        )
keine Ahnung ob dies so gut ist.
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Was für ein dummer Fehler :D Denn erstmal danke. Ich werde das ganze bei Zeiten mal umbauen und nochmal bescheid geben ob es klappt ;)
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Hier mal der fertige Code Klick

Bin für Verbesserungsvorschläge offen ;) Danke an alle die geholfen haben =)
lunar

"ReadTXT.run" implementierst Du besser so:

Code: Alles auswählen

def run(self):
    with open("log.log", "r") as stream:
        for line in log:
            self.newLineRead.emit(line.strip())
Und da Du die neue Signal-Slot-API bereits zur Deklaration des Signals nutzt, spricht nichts dagegen, sie auch beim Verbinden zu nutzen, so dass Zeile 31ff. als "pushButton.clicked.connect(read.start)" formuliert werden kann.

Zum Schluss noch der obligatorische Hinweis auf PEP 8 (e.g. "MAINGUI" umbenennen), und darauf, dass "pushButton" und "textEdit" keine sonderlich aussagekräftigen Bezeichner sind. :)
Benutzeravatar
DaMutz
User
Beiträge: 202
Registriert: Freitag 31. Oktober 2008, 17:25

mir gefällt die Abhängigkeit der 2 Klassen untereinander nicht!
Erzeuge das 'ReadTXT' in der 'main()' und übergib das Objekt dem 'MainGUI' bei der Erzeugung. Die umgekehrt Kopplung ist meiner Meinung nach nicht nötig. 'ReadTXT' muss nichts von einer GUI wissen. Die '__init__'-Methode der 'ReadTXT' Klassen kannst du dann komplett löschen.
Durch diese Trennung wären die 2 Klassen auch besser einzeln testbar.
lunar

"ReadTXT.__init__()" kann man auch so gleich löschen, der Konstruktor tut ja nichts sinnvolles. Aber ich denke, dass ist nur speziell in diesem Beispiel der Fall, tatsächlich wird da wohl irgendeine Art der Initialisierung stehen (e.g. Übergabe des Dateinamens der Logdatei oder so).

Auch sehe ich kein großes Problem in der „Abhängigkeit der Klassen“. Die gibt es gar nicht, denn "ReadTXT" ist auch jetzt schon vollkommen unabhängig von der GUI-Klasse. Allenfalls hängt die GUI-Klasse von ReadTXT ab, aber das ist mit einem einfachen Refactoring zu lösen, und daher meines Erachtens nicht so tragisch. Zumal das im tatsächlichen Quelltext möglicherweise auch so aussehen muss, nämlich wenn ReadTXT erst in Abhängigkeit von gewissen Eingaben in der GUI (e.g. Dateiname der Logdatei aus einem Dateidialog) erzeugt werden kann.
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Bin mir nicht ganz sicher ob "startButton" und "logEdit" sinnvollere Namen sind ^^ aber ich habe die "__init__" Funktion der "ReadTXT" Klasse gelöscht und das "read" Objekt wird nun von der "main" Funktion vererbt.

Habe noch eine Frage bezüglich der Signale.
Wie stelle ich es am besten an das ich die gleichen Signal von mehreren Klassen aus nutzen kann? Wenn ich z.B das Signal "newLineRead" in einer anderen Klasse noch einmal definieren würde müsste ich auch den Slot in der GUI Klasse neu definieren. Würde aber lieber von Zwei Klassen auf einen Slot zugreifen.

Nun habe ich noch eine Frage die sich eher weniger auf diesen Thread richtet:
Wie bringe ich folgenden Beispiel Code auf 80 Zeichen pro Zeile?

Code: Alles auswählen

self.newLineRead.emit("Hallo, ich bin ein mehr als achtzig Zeichen langer Befehl und möchte ohne Zeilenumbruch ausgegeben werden")
Anbei noch der, wie oben beschrieben, veränderte Code.

Code: Alles auswählen

from PySide import QtCore, QtGui
import sys, time


class MainGui(QtGui.QMainWindow):
    def __init__(self, read):
        QtGui.QMainWindow.__init__(self)

        widget = QtGui.QWidget(self)
        widget.setLayout(QtGui.QVBoxLayout(widget))
        self.setCentralWidget(widget)

        logEdit = QtGui.QPlainTextEdit(widget)
        logEdit.setObjectName("textEdit")
        widget.layout().addWidget(logEdit)

        startButton = QtGui.QPushButton(widget)
        startButton.setObjectName("pushButton")
        widget.layout().addWidget(startButton)

        startButton.setText(
                   QtGui.QApplication.translate("Form",
                                                 "Start",
                                                  None,
                                            QtGui.QApplication.UnicodeUTF8
                                            )
                   )

        startButton.clicked.connect(read.start)
        read.newLineRead.connect(logEdit.appendPlainText)


class ReadTXT(QtCore.QThread):
    newLineRead = QtCore.Signal(unicode)

    def run(self):
        file = open("log.log", "r")
        lines = file.readlines()
        file.close()

        for line in lines:
            self.newLineRead.emit(line.strip())
            time.sleep(0.01)


def main():
    app = QtGui.QApplication(sys.argv)
    read = ReadTXT()
    widget = MainGui(read)
    widget.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
lunar

Bitte gewöhne Dir die korrekte Terminologie an. In Deinem Beispiel wird das an "read" gebundene Objekt von "main()" an "MainGUI.__init__()" übergeben, mit Vererbung hat das nicht das Geringste zu tun. Vererbung ist eine Beziehung zwischen Klassen.

Deine Frage bezüglich der Signale verstehe ich nicht, oder anders gesagt, ich kann das beschriebene Problem nicht nachvollziehen. Du kannst doch ganz einfach mehrere, identische oder auch ganz verschiedene Funktionen oder Methoden mit einem Signal verbinden. Wenn Du Methoden zwischen Klassen "teilen" möchtest, in den Sinne, dass beide Klassen diese Methode in identischer Implementierung besitzen, dann hat das nichts mit Signalen und Slots, sondern nur mit den absoluten Grundlagen der Objektorientierung zu tun, die Lösung nämlich heißt Vererbung (und zwar im eigentlichen objektorientierten Sinne, und nicht in dem Sinne, welchem Du diesem Terminus in Deinem Beitrag verpasst hast).
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Sorry wegen der falschen Terminologie.. Werde in Zukunft besser nachdenken bevor ich schreibe ;)

Also ich mochte von Zwei Klassen auf das selbe Signal zugreifen. Ich habe in meinem echten Programm 8 Signale die ich in zwei klassen brauche, das heißt ich muss in der GUI Klasse 16 connect's machen. Ich habe mich nun mal an einem Beispiel mit Vererbung versucht... Das ganze läuft aber nicht wirklich... Klick
Ich habe also eine Talker Klasse erstellt welche das Signal beinhaltet und diese an drei Klassen Vererbt,(Dieses mal ist es sogar richtig :P) an meine GUI Klasse und an zwei Klassen die etwas in das Textfeld einfügen sollen. Nun mault der Interpreter aber rum:
Traceback (most recent call last):
File "C:\scripts\mainExample.py", line 61, in <module>
main()
File "C:\scripts\mainExample.py", line 56, in main
widget = MainGui(read)
File "C:\scripts\mainExample.py", line 34, in __init__
Talker.writeNewLine.connect(logEdit.appendPlainText)
AttributeError: 'PySide.QtCore.Signal' object has no attribute 'connect'
Denke mal die Talker Klasse wird nicht richtig initialisiert sodass das Signal nicht richtig erstellt wird... Wie geht es richtig?

Edit: Habe gerade herausgefunden dass ich das "Talker" in den Klassen durch self ersetzen muss :D Insgesamt wird aber trotzdem nichts in das Textfeld geschrieben. Denke mal dass ich wieder mehrere Signale erstelle und er deswegen nichts empfängt.
lunar

Wie in der Dokumentation beschrieben ist, müssen Klassen, die Signale verwenden, von "QObject" ableiten. Davon abgesehen ist Vererbung wirklich zu viel des Guten, wenn es wirklich nur darum geht, identische Signale in mehreren Klassen zu deklarieren. Da kannst Du die Deklaration auch einfach in jeder Klasse hinzufügen, Vererbung ist nur sinnvoll, wenn wirklich eine signifikante Menge an Quelltext zwischen Klassen zu teilen ist.

Davon abgesehen verstehe ich Dein Problem noch immer nicht. Im vorherigen Beitrag ging es um Slots, die in Python letztlich nichts weiter als normale Methoden sind, jetzt aber geht es Dir auf einmal um Signale, und die sind eben durchaus besondere Attribute.

Im Übrigen ist es eigentlich Grundwissen über Objektorientierung, dass man auf Exemplarattribute mittels "self" zugreift. Das muss Dir klar sein, bevor Du graphische Oberflächen entwickeln möchtest.
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Hmm ich werde jetzt einfach in beiden "worker" Klassen die Signale definieren. Ist denke ich einfacher...
Und wie ich self benutze weiß ich natürlich :D Mir war nur nicht ganz das Ausmaß der Vererbung bewusst. Also was dort geschieht.

Denn mal Danke für die Hilfe =)
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Ich bin gerade auf noch ein Problem gestoßen. Ich brauche zum Beispiel in einer worker Klasse den Text aus einem QLineEdit. Wie stelle ich das ganze an?
lunar

@JonasR: Du musst eben den Text des Steuerelements irgendwie an das "Worker"-Exemplar übergeben.
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Naja das war mir klar nur blieb ich auch an dem "irgendwie" hängen ^^

Soll ich in dem worker ein Signal senden welches der GUI Klasse sagen soll dass sie das QLineEdit ausliest und in eine var der worker Klasse schreiben soll?
Weiß halt nicht ob es dafür eine gute Lösung gibt...
lunar

Wieso übergibst Du den Text nicht einfach bei der Erzeugung des "Worker"-Exemplars?
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Weil sich das Ganze während der Laufzeit noch ändern kann.
lunar

@JonasR: Dann sende doch einfach ein Signal an den Thread, welchem Du den Text des Steuerelements als Parameter mitgibst.
Antworten