Infinite Loop mit QTextedit

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Hallo allerseits,

ich programmiere gerade einen kleinen Editor mit syntax highlighting. Dabei greife ich auf QTextEdit zurück, bei dem dem HTML Elemente mittels setHtml() übermitteln kann.

Dabei bin ich nun auf ein Problem gestoßen. Im Konstruktor des Widgets rufe ich sowas auf:

Code: Alles auswählen

self.editor.textChanged.connect(self.update_text)
update_text wiederum macht folgendes

Code: Alles auswählen

self.editor.setHtml(html)
Dadurch ergibt sich eine Schleife, die irgendwann abbricht, weil der python stack zu voll ist.

QLineEdit besitzt eine Funktion bei der man den Text ändern kann und kein Signal emittiert wird. Bei QTextEdit hab ich sowas nicht gefunden.

Auch das disconnecten des Signal davor bringt meine Python Version zum Absturz (ohne Fehlermeldung.): Also sowas in der art:

Code: Alles auswählen

    def update_text(self):
        self.editor.textChanged.disconnect()
        self.editor.setHtml("html")
        self.editor.textChanged.connect(self.update_text)
Hat da jemand Vorschläge?

Grüße,
anogayales
lunar

@anogayales: Welche Methode von "QLineEdit" meinst Du? "QLineEdit.setText" löst ebenfalls "QLineEdit.textChanged" aus, alles andere wäre auch reichlich überraschend.

Die Verbindung zum Signal zu trennen, ist eine Möglichkeit, aber bitte korrekt. Dein Versuch trennt alle möglichen Verbindungen, stellt danach aber nur eine einzige wieder her. Trenne also nur den einen, speziellen Slot mit "self.editor.textChanged.disconnect(self.update_text)". Falls das Deine Python-Version zum Absturz bringt, wäre es sinnvoll, die Python-Version samt der verwendeten Anbindung zu erwähnen.

Die andere Möglichkeit ist so offensichtlich, dass ich überrascht bin, sie extra erwähnen zu müssen. Nutze ein Attribut in der umgebenden Klasse, um die Rekursion zu verhindern:

Code: Alles auswählen

def update_text(self):
    if self._is_updating_text:
        return
    self._is_updating_text = True
    self.editor.setHtml('<h1>Spam with eggs</h1>')
    self._is_updating_text = False
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

lunar hat geschrieben:@anogayales: Welche Methode von "QLineEdit" meinst Du? "QLineEdit.setText" löst ebenfalls "QLineEdit.textChanged" aus, alles andere wäre auch reichlich überraschend.
Das hier habe ich gemeint. Sorry wenn ich mich missverständlich ausgedrückt habe.
When the text changes the textChanged() signal is emitted; when the text changes other than by calling setText() the textEdited() signal is emitted;
Aber das tut hier ja nichts zur Sache, da ich a QTextEdit benutzen muss (HTML support).
Die Verbindung zum Signal zu trennen, ist eine Möglichkeit, aber bitte korrekt. Dein Versuch trennt alle möglichen Verbindungen, stellt danach aber nur eine einzige wieder her. Trenne also nur den einen, speziellen Slot mit "self.editor.textChanged.disconnect(self.update_text)". Falls das Deine Python-Version zum Absturz bringt, wäre es sinnvoll, die Python-Version samt der verwendeten Anbindung zu erwähnen.
So ich habs nochmal getestet. Jetzt kommt zumindest kein Fehler. Man kann aber leider auch nichts editieren, da es mir scheint, dass der Cursor immer an die Ausgangsposition springt. Auch einfaches eintippen ist nicht möglich.
lunar hat geschrieben: Die andere Möglichkeit ist so offensichtlich, dass ich überrascht bin, sie extra erwähnen zu müssen. Nutze ein Attribut in der umgebenden Klasse, um die Rekursion zu verhindern:

Code: Alles auswählen

def update_text(self):
    if self._is_updating_text:
        return
    self._is_updating_text = True
    self.editor.setHtml('<h1>Spam with eggs</h1>')
    self._is_updating_text = False
Auch schon getestet, leider tritt das gleiche Problem wie oben auf. Meine Python Version: 2.6.6. PyQt4 Version: 4.8.1


Falls ein Minimalbeispiel erwünscht ist, so kann ich dieses gerne liefern.

Grüße,
anogayales
lunar

@anogayales: Im Bezug auf "QLineEdit" sprichst Du offenbar von "QLineEdit.textEdited". Dabei handelt es sich um ein Signal, nicht um eine Methode. Ein solches Signal gibt es bei "QTextEdit" nicht, da der Text eines "QTextEdit" auf verschiedene Art verändert und editiert werden kann, so dass man nicht mehr ursächlich zwischen Editieren und programmatischer Veränderung trennen kann.

"QTextEdit.setHtml()" aktualisiert den Text nicht, sondern überschreibt den vorherigen Inhalt mit dem übergebenen Text. Der Cursor wird dabei logischerweise zurückgesetzt. Dieses Verhalten ist dokumentiert und sollte nicht weiter verwundern. ".setHtml()" dient mithin nicht zur Veränderung des bereits enthaltenen Texts, sondern zum vollständigen Ersatz desselben.

Zur Veränderung des enthaltenen Texts unter Beibehaltung der Cursorposition kannst Du entweder ".insertHtml()" nutzen, oder die logische Struktur des enthaltenen Texts über ".document()" und die "QTextDocument"-API direkt manipulieren.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

lunar hat geschrieben: "QTextEdit.setHtml()" aktualisiert den Text nicht, sondern überschreibt den vorherigen Inhalt mit dem übergebenen Text. Der Cursor wird dabei logischerweise zurückgesetzt. Dieses Verhalten ist dokumentiert und sollte nicht weiter verwundern. ".setHtml()" dient mithin nicht zur Veränderung des bereits enthaltenen Texts, sondern zum vollständigen Ersatz desselben.
"
Vielen Dank für deine Antwort. Hintergrund ist, dass ich mit pygments aus einem Plaintext eine HTML "Datei" erstellen lasse. Und da ist es ja am einfachsten einfach den ganzen Text zu ersetzen oder sehe ich da was falsch? Kompliziert rauszufinden, welche Teile sich durch meinen Edit ändern halte ich für ein bisschen zu viel des Guten. Mach ich mir mal wieder alles zu kompliziert?

Grüße,
anogayales
lunar

@anogayales: Du machst es Dir zu einfach. So trivial ist syntaktische Hervorhebung nicht zu implementieren, schon weil man vermeiden möchte, bei jeder Änderung den gesamten Text zu parsen.

Ich würde Dir erst einmal dazu raten, einfach QScintilla zu verwenden, statt Deine eigene Hervorhebung zu implementieren. Was für Eric gut genug ist, wird für Deine Anwendung schon nicht schlecht sein. Falls QScintilla sich als ungeeignet erweist, oder Du unbedingt pygments nutzen möchtest, dann leite von "QSyntaxHighlighter", um die syntaktische Hervorhebung zu implementieren.

Allerdings musst Du dazu wesentlich tiefer in die pygments-API einsteigen, anstatt einfach "pygments.highlight()" aufzurufen, wie Du es jetzt wahrscheinlich tust. "QSyntaxHighlighter" arbeitet nicht mit HTML, sondern mit der bereits erwähnten "QTextDocument"-API. Du musst den Quelltext also manuell mit "pygments.lex" zerlegen, und anschließend jedem Token mit "QSyntaxHighlighter.setFormat()" das entsprechende Format zuweisen. Das Format kannst Du aus dem HTML-Formatter übernehmen und auf "QTextCharFormat" abbilden. Dabei musst Du allerdings beachten, dass "QSyntaxHighlighter" nicht den gesamten Text, sondern nur einzelne Blöcke erfasst. Du musst also den Kontext berücksichtigen, z.B. ob der aktuelle Block in einem Docstring liegt oder nicht (siehe "QSyntaxHighlighter.previousBlockState()"). Das ist nicht einfach, aber machbar.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Vielen Dank für die ausführliche Antwort. Das werde ich definitiv auf meine To Do Liste schreiben.

Grüße,
anogayales
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Hi,
ohne 100% genau gelsen zu haben, was spricht gegen die Methode Qbject.blockSignals(true) ....

http://doc.qt.nokia.com/stable/qobject. ... ockSignals

Gruß
lunar

@ichisich: Das ursprüngliche Problem, bei welchem diese Methode helfen könnte, ist längst geklärt.

Gegen diese Methode spricht, dass sie ebenso grob ist wie ein unspezifisches ".disconnect()", und somit auch legitime Empfänger dieses Signals ausschließt.
Antworten