Hallo,
ich bin langzeitiger Leser und nun zum ersten mal Poster hier, da ich nun bei meinem Problem schon längere Zeit anstehe.
Nun zu meinem Projekt: Ich habe einen Art Editor programmiert der auch syntax-Highlighting unterstützen soll.
Die Schlüsselwörter werden vorher aus einer XML Datei eingelesen: Es gibt Kommandos, Funktionen, spezielle Variablen, Kommentare und noch ein paar andere... Das ist aber für mein eigentliches Problem nicht so wichtig.
Also:
Ich habe eine JTextPane der ich ein javax.swing.text.DefaultStyledDocument (ab jetzt: doc) zuweise.
Dieses doc erzeuge ich mir mit dem Konstruktor der einen StyleContext nimmt. In diesem StyleContext sind meine Styles die später zuweisen werde abgelegt. Die Stylehierarchie ist nichts Besonders. -> Alle Styles haben als "parent"-Style den DefaultStyle und unerscheiden sich nur im Attribut "Foreground" (also in meinem Fall, Textfarbe) von einander.
Nun gibt es in meinem Editor noch ein scanner-Objekt, dass bestimmt welche Farbe/Style welche Zeichen haben sollen.
Diese Scanner-Klasse ist erneut nichts großartiges, und macht auch keinen großen Rechenaufwand.
Diesem scanner-objekt gebe ich noch mein DefaultStyleDocument bekannt (es bekommt eine Referenz).
Auf dem Document habe ich einen DocumentListener regestriert der nichts anderes macht als bei insertUpdate (es gibt noch changeUpdate und removeUpdate) die Methode scan() auf dem scannerObjekt aufzurufen.
Die scan Methode schnappt sich mit doc.getText() den Inhalt (string) des docs (also gleichzeitig den Inhalt der JTextPane) und parst diesen. Jedes mal wenn ein vollständiger Token erkannt wurde setze ich mit
für diesen Abschnitt/Token den Style mit der richtigen Farbe. drawFrom ist hierbei der Anfang des tokens, pos die Länge; mit this.doc.getStyle(String) hole ich mir den entsprechenden Style aus dem DefaultStyleDocument, dem ich im Konstruktor meinen vorbereiteten StyleContext übergeben habe, true -> heißt das ich die bisherigen Attribute für diesen Abschnitt überschreiben will) ""+this.token, desshalb weil mein token nur ein int ist, dieser int legt fest um welche Art von Schlüsselwort es sich handelt. Die Stylenamen sind einfach Nummern: "1", "2".. etc..
Ok - so weit so gut. Es funktioniert grundsätzlich - nur elend langsam.
Falls ich das scan() direkt im AWT-Thread der das insertUpdate des DocumentListeners kommt wie zu erwarten, (oder wenn man gleich mit copy & paste ganze textblöcke einfügt) eine "Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification". Die setCharacterAttributes() ist nämlich "thread-sicher" im Gegensatz zu den meisten anderen Swing-Methoden.
Also habe ich das scannen und setzen der Styles in einen eigenen Thread ausgelagert. (Bei jedem insertupdate wird ein neuer Thread angelegt).
Jetzt flackert der Text bei jedem Tastendruck wie wild herum, da die Styles immer neu gesetzt werden. Außerdem geht die Prozessorauslastung beim Einfügen größere Textblöcke sehr schnell auf 100%. (10 Zeilen kopieren und dann schnell 10 mal STRG+V und man kann mit eigenen Augen zusehen wie er die Styles setzt :/, diesen Test nenne ich ab jetzt "schnelles Einfügen")
Um das "Flackern" (in der JTextPane) wegzubekommen habe ich versucht DoubleBuffered auf true zu setzen -> kein erfolg.
Nun habe ich das Scannen und Setzen mit SwingUtilities.invokeLater(new Runnable() { ... }); probiert, also wieder vom AWT-Thread machen zu lassen.
Das Flackern war weg, aber dafür war es noch unperformanter. Und wenn man jetzt das "schnelle Einfügen" gemacht hat dann war die ganze GUI für 2-3 Sekunden nicht ansprechbar weil der AWT Thread beschäftigt war.
Ein weiter Ansatz der nicht wirklich was gebracht hat war einen Thread der beim Erzeugen der Scannerklasse ebenfalls erstellt wurde und dann mit Hilfe eines Objects und eines boolean so synchronisiert wurde, dass er immer schläft außer es tritt ein insertUpdate am Dokument auf: läuft in einer Endlosschleif als Daemon und wird immer dann vom AWT-Thread aufgeweckt wenn das Dokument verändert wurde, der awt-Thread setzt also das parse-boolean auf true weckt den Scanner-Thread auf und ist fertig mit insertUpdate. Der ScannerThread scannt und setzt die Styles setzt den parse boolean auf false und legt sich wieder schlafen. -> Kein Performancegewinn, Flacker vermindert aber nicht weg; außerdem wurde oft das letze Schlüsselwort beim copy & paste nicht richtig gehighlightet da manche insertUpdate( wo ich den parse boolean auf true setze und den thread aufwecke (notifyAll() einfach verpufft sind, da der Thread noch nicht wieder mit dem Scannen fertig war, aber schon ein neues insertUpdate-Event ausgelöst wurde
Ein ganz ein anderer Ansatz -> der vorher schon einma perfekt funktioniert hat:
Das Dokument wird immer dann neu geparst wenn der Text in der JTextPane neu gezeichnet werden muss.
Es wurde die API-Methode drawUnselectedText(Graphics g) überschrieben und vor jedem "Zeichnen" (am Bildschirm) die aktuelle Zeichenfarbe auf die gewünschte Farbe gesetzt. So wurde dann Token für Token auf den Bildschirm gezeichnet. Das war flacker und ruckelfrei möglich. Daher bin ich mir auch ziemlich sicher, dass die Scan-Funktion nicht die Bremse/der große Aufwand ist, da die drawUnselectedText nicht nur aufgerufen wurde wenn sich der Inhalt des Dokuments verändert hat sondern immer dann wenn der Text neu gezeichnet werden muss. (also zB. auch dann wenn das Fenster verschoben wird oder man mit dem Mauszeiger darüberfährt)
Ich hab den Ansatz dann verworfen, weil ich den Editor noch erweitern will (Autovervollständigung) und es sehr schwer ist, dann hinter die gezeichneten Tokens noch Logik zu legen. Generell ist es kein schönes Konzept. (nicht Java )
Sachen die ich bereits probiert habe, keinen Unterschied/Erfolg gebracht haben:
die JTextPane.setDoubleBuffered(true); setzen
eigener Thread für jeden Parsevorgang
SwingUtilites.invokeLater()
Einzelner Thread der parst und sich dann wieder schlafen legt.
(Zeichenfarbe vor jedem Token setzen *pfui* schmutzig)
Sachen die ich noch nicht ausprobiert habe, weil ich nicht glaube, dass sie was bringen:
einen eigenen Thread (entweder einzelner mit Schlafen legen oder immer neuen pro Parsen) fürs Parsen der statt dass er die Styles setzt die Position(start, ende) und Schlüsselwort eine Collection anlegt und dann mit SwingUtilities.invokeLater() dem AWT Thread sagt, dass dieser diese Styles setzten soll -> (ich glaube nicht, dass das Scannen die Bremse ist, siehe oben, ich glaube der Flaschenhals ist das setzen der Styles)
Mach ich vom Konzept her etwas falsch? - Ich weiß echt nicht mehr weiter. Hab extra schon wie inder API beschrieben ein Document mit StyleContext, damit für alle Textabschnitte mit gleicher Farbe auch das gleiche Style-Objekt verwendet wird, erstellt.
Falls sich wirklich jemand die Mühe machne würde und sich den Code anschauen will, ich kann ihn soweit es geht vom bestehenden Code trennen (so viel, wie es sich vielleicht anhört ist es auch wieder nicht) (Scanner-Klasse liefert dann halt nur Random-dummy Farben). -> Ich wäre demjenigen dann ewig dankbar. Anbieten kann ich euch für eure Hilfe leider nichts. (Ist bloß ein freiwillges Projekt eines Klassenkameradens und mir für die Schule),
Ein weiterer Versuch wäre ein Thread der das Dokument einfach in gewissen Zeitabstand scannt. Ich glaub, da tritt das Problem des flackerns aber wieder auf
Falls irgendetwas noch unklar: Einfach hier posten.
Ich bin für jede Hilfe dankbar -> weiß inzwischen echt nicht mehr weiter.
Youlian
ich bin langzeitiger Leser und nun zum ersten mal Poster hier, da ich nun bei meinem Problem schon längere Zeit anstehe.
Nun zu meinem Projekt: Ich habe einen Art Editor programmiert der auch syntax-Highlighting unterstützen soll.
Die Schlüsselwörter werden vorher aus einer XML Datei eingelesen: Es gibt Kommandos, Funktionen, spezielle Variablen, Kommentare und noch ein paar andere... Das ist aber für mein eigentliches Problem nicht so wichtig.
Also:
Ich habe eine JTextPane der ich ein javax.swing.text.DefaultStyledDocument (ab jetzt: doc) zuweise.
Dieses doc erzeuge ich mir mit dem Konstruktor der einen StyleContext nimmt. In diesem StyleContext sind meine Styles die später zuweisen werde abgelegt. Die Stylehierarchie ist nichts Besonders. -> Alle Styles haben als "parent"-Style den DefaultStyle und unerscheiden sich nur im Attribut "Foreground" (also in meinem Fall, Textfarbe) von einander.
Nun gibt es in meinem Editor noch ein scanner-Objekt, dass bestimmt welche Farbe/Style welche Zeichen haben sollen.
Diese Scanner-Klasse ist erneut nichts großartiges, und macht auch keinen großen Rechenaufwand.
Diesem scanner-objekt gebe ich noch mein DefaultStyleDocument bekannt (es bekommt eine Referenz).
Auf dem Document habe ich einen DocumentListener regestriert der nichts anderes macht als bei insertUpdate (es gibt noch changeUpdate und removeUpdate) die Methode scan() auf dem scannerObjekt aufzurufen.
Die scan Methode schnappt sich mit doc.getText() den Inhalt (string) des docs (also gleichzeitig den Inhalt der JTextPane) und parst diesen. Jedes mal wenn ein vollständiger Token erkannt wurde setze ich mit
Code:
this.doc.setCharacterAttributes(drawFrom, pos, this.doc.getStyle(""+this.token), true);
Ok - so weit so gut. Es funktioniert grundsätzlich - nur elend langsam.
Falls ich das scan() direkt im AWT-Thread der das insertUpdate des DocumentListeners kommt wie zu erwarten, (oder wenn man gleich mit copy & paste ganze textblöcke einfügt) eine "Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification". Die setCharacterAttributes() ist nämlich "thread-sicher" im Gegensatz zu den meisten anderen Swing-Methoden.
Also habe ich das scannen und setzen der Styles in einen eigenen Thread ausgelagert. (Bei jedem insertupdate wird ein neuer Thread angelegt).
Jetzt flackert der Text bei jedem Tastendruck wie wild herum, da die Styles immer neu gesetzt werden. Außerdem geht die Prozessorauslastung beim Einfügen größere Textblöcke sehr schnell auf 100%. (10 Zeilen kopieren und dann schnell 10 mal STRG+V und man kann mit eigenen Augen zusehen wie er die Styles setzt :/, diesen Test nenne ich ab jetzt "schnelles Einfügen")
Um das "Flackern" (in der JTextPane) wegzubekommen habe ich versucht DoubleBuffered auf true zu setzen -> kein erfolg.
Nun habe ich das Scannen und Setzen mit SwingUtilities.invokeLater(new Runnable() { ... }); probiert, also wieder vom AWT-Thread machen zu lassen.
Das Flackern war weg, aber dafür war es noch unperformanter. Und wenn man jetzt das "schnelle Einfügen" gemacht hat dann war die ganze GUI für 2-3 Sekunden nicht ansprechbar weil der AWT Thread beschäftigt war.
Ein weiter Ansatz der nicht wirklich was gebracht hat war einen Thread der beim Erzeugen der Scannerklasse ebenfalls erstellt wurde und dann mit Hilfe eines Objects und eines boolean so synchronisiert wurde, dass er immer schläft außer es tritt ein insertUpdate am Dokument auf: läuft in einer Endlosschleif als Daemon und wird immer dann vom AWT-Thread aufgeweckt wenn das Dokument verändert wurde, der awt-Thread setzt also das parse-boolean auf true weckt den Scanner-Thread auf und ist fertig mit insertUpdate. Der ScannerThread scannt und setzt die Styles setzt den parse boolean auf false und legt sich wieder schlafen. -> Kein Performancegewinn, Flacker vermindert aber nicht weg; außerdem wurde oft das letze Schlüsselwort beim copy & paste nicht richtig gehighlightet da manche insertUpdate( wo ich den parse boolean auf true setze und den thread aufwecke (notifyAll() einfach verpufft sind, da der Thread noch nicht wieder mit dem Scannen fertig war, aber schon ein neues insertUpdate-Event ausgelöst wurde
Ein ganz ein anderer Ansatz -> der vorher schon einma perfekt funktioniert hat:
Das Dokument wird immer dann neu geparst wenn der Text in der JTextPane neu gezeichnet werden muss.
Es wurde die API-Methode drawUnselectedText(Graphics g) überschrieben und vor jedem "Zeichnen" (am Bildschirm) die aktuelle Zeichenfarbe auf die gewünschte Farbe gesetzt. So wurde dann Token für Token auf den Bildschirm gezeichnet. Das war flacker und ruckelfrei möglich. Daher bin ich mir auch ziemlich sicher, dass die Scan-Funktion nicht die Bremse/der große Aufwand ist, da die drawUnselectedText nicht nur aufgerufen wurde wenn sich der Inhalt des Dokuments verändert hat sondern immer dann wenn der Text neu gezeichnet werden muss. (also zB. auch dann wenn das Fenster verschoben wird oder man mit dem Mauszeiger darüberfährt)
Ich hab den Ansatz dann verworfen, weil ich den Editor noch erweitern will (Autovervollständigung) und es sehr schwer ist, dann hinter die gezeichneten Tokens noch Logik zu legen. Generell ist es kein schönes Konzept. (nicht Java )
Sachen die ich bereits probiert habe, keinen Unterschied/Erfolg gebracht haben:
die JTextPane.setDoubleBuffered(true); setzen
eigener Thread für jeden Parsevorgang
SwingUtilites.invokeLater()
Einzelner Thread der parst und sich dann wieder schlafen legt.
(Zeichenfarbe vor jedem Token setzen *pfui* schmutzig)
Sachen die ich noch nicht ausprobiert habe, weil ich nicht glaube, dass sie was bringen:
einen eigenen Thread (entweder einzelner mit Schlafen legen oder immer neuen pro Parsen) fürs Parsen der statt dass er die Styles setzt die Position(start, ende) und Schlüsselwort eine Collection anlegt und dann mit SwingUtilities.invokeLater() dem AWT Thread sagt, dass dieser diese Styles setzten soll -> (ich glaube nicht, dass das Scannen die Bremse ist, siehe oben, ich glaube der Flaschenhals ist das setzen der Styles)
Mach ich vom Konzept her etwas falsch? - Ich weiß echt nicht mehr weiter. Hab extra schon wie inder API beschrieben ein Document mit StyleContext, damit für alle Textabschnitte mit gleicher Farbe auch das gleiche Style-Objekt verwendet wird, erstellt.
Falls sich wirklich jemand die Mühe machne würde und sich den Code anschauen will, ich kann ihn soweit es geht vom bestehenden Code trennen (so viel, wie es sich vielleicht anhört ist es auch wieder nicht) (Scanner-Klasse liefert dann halt nur Random-dummy Farben). -> Ich wäre demjenigen dann ewig dankbar. Anbieten kann ich euch für eure Hilfe leider nichts. (Ist bloß ein freiwillges Projekt eines Klassenkameradens und mir für die Schule),
Ein weiterer Versuch wäre ein Thread der das Dokument einfach in gewissen Zeitabstand scannt. Ich glaub, da tritt das Problem des flackerns aber wieder auf
Falls irgendetwas noch unklar: Einfach hier posten.
Ich bin für jede Hilfe dankbar -> weiß inzwischen echt nicht mehr weiter.
Youlian