D) Formatierte Texteingabe
Wenn Sie editierbaren Text in Java darstellen möchten, haben Sie mehrere Möglichkeiten. Sie können bspw. eine JTextArea
oder ein JTextField
verwenden und simple Formatierungen daran vornehmen, die das komplette Textfeld betreffen (bspw. die Hintergrundfarbe oder Schrift verändern). Sollen sich bestimmte Textabschnitte voneinander unterscheiden, haben Sie zuerst einmal ein Problem, weil das mit einer JTextArea
oder einem JTextField
nicht funktioniert. Eine Alternative wäre es, HTML in einem JEditorPane
darzustellen. Die Editierbarkeit ist in diesem Fall jedoch stark eingeschränkt. Es gibt noch eine weitere Möglichkeit: Sie können ein javax.swing.JTextPane
mit einem eigenen javax.swing.text.StyledDocument
versehen. Diese Variante wird Ihnen in diesem Kapitel vorgestellt.
Ziel
Das Ziel dieses Kapitels ist es, ein StyledDocument
zu entwickeln, mit dem Sie Text in einem JTextPane
individuell formatiert darstellen können. Außerdem bekommen Sie noch eine kleine GUI-Komponente, mit der Sie die Funktionalität des StyledDocuments
in einer JTextPane
ausprobieren können.
Beachten Sie jedoch, dass es in diesem Kapitel in erster Linie um das StyledDocument
und nicht die grafische Darstellung geht. Die GUI-Komponente wird folglich nicht bis ins letzte Detail ausgereift sein und deren Funktionsweise nicht weiter erläutert werden.
Das StyledDocument
Ein StyledDocument
formatiert den Inhalt einer Text-Komponente, die mit einem solchen Document
umgehen kann. Hierzu gehört bspw. die javax.swing.JTextPane
. Wie genau so ein Document
funktioniert, können Sie detailliert in der Java API-Dokumentation nachlesen. Der Text-Komponente wird das Document
gesetzt. Anschließend kann (über den Aufruf von Methoden des Documents
) die Darstellung der Text-Komponente an beliebigen Stellen verändert werden.
Unser Document
nennen wir EditorDocument
und lassen es direkt von javax.swing.text.DefaultStyledDocument
erben. Das DefaultStyledDocument
nimmt uns im Vergleich zum Interface StyledDocument
schon einige Arbeit ab.
package de.jbb.style; import javax.swing.text.DefaultStyledDocument; public class EditorDocument extends DefaultStyledDocument {}
Um nun bspw. die Schriftart für eine bestimmte Textstelle zu verändern, erzeugen wir eine Methode, der die neue Schriftart übergeben wird. Zusätzlich muss natürlich noch spezifiziert werden, ab welcher Position (start
) und bis zu welcher Position (end
) die Schriftart verändert werden soll.
public void setFont(int start, int end, String font) {}
Für dieses Vorhaben benötigen Sie zuerst ein javax.swing.text.SimpleAttributeSet
. Dieses SimpleAttributeSet
definiert, wie die Textstelle formatiert sein soll (bspw. fett, kursiv, 16 Punkte groß, …).
SimpleAttributeSet sas = new SimpleAttributeSet();
Der nächste Schritt besteht darin, dem SimpleAttributeSet
die neue Schriftart zu setzen. Hierzu wird die Klasse javax.swing.text.StyleConstants
verwendet. Diese Klasse bietet mehrere statische Methoden, mit welchen das SimpleAttributeSet
manipuliert werden kann. Um die Schriftart zu verändern, wird die Methode setFontFamily
mit dem SimpleAttributeSet
und dem Namen der gewünschten Schriftart aufgerufen.
StyleConstants.setFontFamily(sas, font);
Natürlich muss das manipulierte SimpleAttributeSet
noch den entsprechenden Zeichen zugeordnet werden. Dies geschieht mit dem Aufruf setCharacterAttributes(int offSet, int length, AttributSet s, boolean replace)
der Klasse DefaultStyledDocument
.
offset
= Position, ab der die Zeichen formatiert werden sollen
length
= Anzahl der zu formatierenden Zeichen
s
= Formatierung der Zeichen als SimpleAttributeSet
replace
= true
, falls die alten Formatierungen ersetzt werden sollen
setCharacterAttributes(start, end - start, sas, false);
Daraus resultiert die Methode
public void setFont(int start, int end, String font) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setFontFamily(sas, font); setCharacterAttributes(start, end - start, sas, false); }
Anhand dieser Methode fällt es nun nicht mehr schwer, weitere abzuleiten:
// kursiv public void setItalic(int start, int end, boolean active) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setItalic(sas, active); setCharacterAttributes(start, end - start, sas, false); } // fett public void setBold(int start, int end, boolean active) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setBold(sas, active); setCharacterAttributes(start, end - start, sas, false); } // unterstrichen public void setUnderline(int start, int end, boolean active) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setUnderline(sas, active); setCharacterAttributes(start, end - start, sas, false); } // Schriftgröße public void setFontSize(int start, int end, int size) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setFontSize(sas, size); setCharacterAttributes(start, end - start, sas, false); } // Textfarbe public void setForeground(int start, int end, Color col) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setForeground(sas, col); setCharacterAttributes(start, end - start, sas, false); } // Hintergrundfarbe public void setBackground(int start, int end, Color col) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setBackground(sas, col); setCharacterAttributes(start, end - start, sas, false); }
Die Attribute fett, kursiv und unterstrichen können jeweils nur zwei Zustände annehmen – aktiv oder inaktiv. Ob ein Text nun bspw. unterstrichen oder der Unterstrich entfernt werden soll, spezifiziert der Parameter boolean active
. Um zwischen den beiden Zuständen zu wechseln, sollten Sie noch eine Methode bereitstellen, die den aktuellen Zustand einer Textstelle auslesen kann (bspw. fett oder nicht fett). Hierzu kommen Sie mit der Methode getCharacterElement(pos)
des DefaultStyledDocument
an ein javax.swing.text.Element
. Aus diesem können Sie wiederum die aktuell gesetzten Formatierungen über die Methode getAttributes
auslesen. Die spezifischen Formatierungen (fett, kursiv, Schriftgröße, …) dieses Zeichens werden über eine statische Methode der bereits bekannten Klasse StyleConstants
abgefragt.
public boolean isItalic(int pos) { Element element = getCharacterElement(pos); return StyleConstants.isItalic(element.getAttributes()); } public boolean isBold(int pos) { Element element = getCharacterElement(pos); return StyleConstants.isBold(element.getAttributes()); } public boolean isUnderline(int pos) { Element element = getCharacterElement(pos); return StyleConstants.isUnderline(element.getAttributes()); }
Als Letztes soll es noch die Möglichkeit geben, Bilder in den Text einzufügen. Dies ist prinzipiell erst einmal ähnlich wie bei den bereits bekannten Methoden. Der einzige Unterschied besteht darin, dass ein Bild natürlich kein „CharacterAttribute“ ist. Deshalb kann hier nicht die Methode setCharacterAttributes
verwendet werden. Stattdessen greifen wir auf die Methode insertString(int position, String str, AttributeSet as)
der Klasse DefaultStyledDocument
zu, welche den Inhalt des Dokuments manipulieren kann.
public void setIcon(int pos, ImageIcon ico) throws BadLocationException { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setIcon(sas, ico); insertString(pos, " ", sas); }
Hiermit ist unser EditorDocument
fertig.
package de.jbb.style; import java.awt.Color; import javax.swing.ImageIcon; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Element; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; public class EditorDocument extends DefaultStyledDocument { private static final long serialVersionUID = -2191570369962370294L; public void setIcon(int pos, ImageIcon ico) throws BadLocationException { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setIcon(sas, ico); insertString(pos, " ", sas); } public boolean isItalic(int pos) { Element element = getCharacterElement(pos); return StyleConstants.isItalic(element.getAttributes()); } public boolean isBold(int pos) { Element element = getCharacterElement(pos); return StyleConstants.isBold(element.getAttributes()); } public boolean isUnderline(int pos) { Element element = getCharacterElement(pos); return StyleConstants.isUnderline(element.getAttributes()); } public void setItalic(int start, int end, boolean active) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setItalic(sas, active); setCharacterAttributes(start, end - start, sas, false); } public void setBold(int start, int end, boolean active) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setBold(sas, active); setCharacterAttributes(start, end - start, sas, false); } public void setUnderline(int start, int end, boolean active) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setUnderline(sas, active); setCharacterAttributes(start, end - start, sas, false); } public void setFont(int start, int end, String font) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setFontFamily(sas, font); setCharacterAttributes(start, end - start, sas, false); } public void setFontSize(int start, int end, int size) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setFontSize(sas, size); setCharacterAttributes(start, end - start, sas, false); } public void setForeground(int start, int end, Color col) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setForeground(sas, col); setCharacterAttributes(start, end - start, sas, false); } public void setBackground(int start, int end, Color col) { SimpleAttributeSet sas = new SimpleAttributeSet(); StyleConstants.setBackground(sas, col); setCharacterAttributes(start, end - start, sas, false); } public EditorDocument() { super(); } public EditorDocument(Content arg0, StyleContext arg1) { super(arg0, arg1); } public EditorDocument(StyleContext arg0) { super(arg0); } }
Text formatieren
Um nun Text formatiert darstellen zu können, müssen Sie ein neues JTextPane
erzeugen, und diesem das EditorDocument
zuweisen.
EditorDocument doc = new EditorDocument(); JTextPane pane = new JTextPane(doc);
Nun können Sie mit den Methoden des EditorDocuments
den Text nach belieben formatieren. Verändern Sie bspw. die Schriftgröße der Zeichen 5 bis 15 in der JTextPane
auf 20 Punkte:
doc.setFontSize(5, 15, 20);
Oder lassen Sie die Zeichen 20 bis 40 fett darstellen:
doc.setBold(20, 40, true);
Sinnvoller ist es natürlich, wenn die Zeichen nicht willkürlich verändert werden. Wenn Sie dem User die Möglichkeit geben, eine bestimmte Textstelle zu formatieren, dann wird das in der Regel die sein, die er momentan mit der Maus markiert hat. Über die Methoden getSelectionStart()
und getSelectionEnd()
des JTextPanes
können Sie die aktuelle Start und End Position der Selektion auslesen.
Testen
Wie am Anfang dieses Kapitels angekündigt, bekommen Sie noch eine Klasse, mit der Sie die grundlegenden Funktionen testen können. Beachten Sie, dass diese wirklich nur zu Testzwecken gedacht und nicht sonderlich ausgereift ist. Es liegt an Ihnen diese Klasse bei Bedarf noch auszubauen!
package de.jbb.style; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GraphicsEnvironment; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextPane; import javax.swing.JToolBar; import javax.swing.filechooser.FileFilter; import javax.swing.text.BadLocationException; public class EditorComponent extends JPanel implements ActionListener, ItemListener { private static final long serialVersionUID = 2232612551746729177L; // Textpane zur Darstellung private JTextPane pane = null; // Unser Document private EditorDocument doc = null; // Button um etwas fett zu markieren private JButton bold = null; // Button um etwas krusiv zu markieren private JButton italic = null; // Button um etwas zu unterstreichen private JButton underline = null; // Button um die Schriftfarbe zu ändern private JButton foreground = null; // Button um die Hintergrundfarbe zu ändern private JButton background = null; // Button um ein Bild einzufügen private JButton pic = null; // JComboBox mit allen verfügbaren Schriftarten private JComboBox fonts = null; // JComboBox mit unterschiedlichen Schriftgrößen private JComboBox size = null; // Toolbar für die Buttons und ComboBoxen private JToolBar bar = null; // JFileChooser um ggf. ein Bild auswählen zu können private JFileChooser imch = null; public EditorComponent() { setLayout(new BorderLayout()); // Anzeigebereich erzeugen this.doc = new EditorDocument(); this.pane = new JTextPane(this.doc); // Tools initialisieren this.bold = new JButton("F"); this.italic = new JButton("I"); this.underline = new JButton("U"); this.foreground = new JButton("SF"); this.background = new JButton("HF"); this.pic = new JButton("Bild"); this.fonts = new JComboBox(); this.size = new JComboBox(); // restliche Komponenten initialisieren this.bar = new JToolBar(); this.imch = new JFileChooser(); // Einen FileFilter für die Bildauswahl setzen // es dürfen nur jpg und png Bilder eingefügt werden this.imch.setFileFilter(new FileFilter() { public boolean accept(File f) { if (f.isDirectory()) { return true; } if (f.getAbsolutePath().toLowerCase().endsWith(".png")) { return true; } if (f.getAbsolutePath().toLowerCase().endsWith(".jpg")) { return true; } return f.getAbsolutePath().toLowerCase().endsWith(".jpeg"); } public String getDescription() { return "Image (*.jpg, *.jpeg, *.png)"; } }); // Verschiedene Schriftgrößen initialisieren for (int i = 8; i < 30; i += 2) { this.size.addItem(i); } // Alle verfügbaren Schriftarten auslesen und in der JComboBox anzeigen String[] font = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); for (int i = 0; i < font.length; i++) { this.fonts.addItem(font[i]); } // GUI zusammenbauen add(this.pane); add(this.bar, BorderLayout.NORTH); this.bar.add(this.bold); this.bar.add(this.italic); this.bar.add(this.underline); this.bar.add(this.fonts); this.bar.add(this.size); this.bar.add(this.foreground); this.bar.add(this.background); this.bar.add(this.pic); // Listener an die Tools hängen this.bold.addActionListener(this); this.italic.addActionListener(this); this.underline.addActionListener(this); this.foreground.addActionListener(this); this.background.addActionListener(this); this.pic.addActionListener(this); this.fonts.addItemListener(this); this.size.addItemListener(this); } public void actionPerformed(ActionEvent evt) { // Start und End Position der Selektion auslesen int start = this.pane.getSelectionStart(); int end = this.pane.getSelectionEnd(); // Falls keine sinnvolle Selektion => kompletten Text bearbeiten if (start >= end) { start = 0; end = this.pane.getText().length(); } // Fett setzen if (evt.getSource() == this.bold) { // Falls das erste, selektierte Zeichen bereits fett dargestellt // wird, die Fett-Formatierung aufheben, ansonsten den selektierten // Bereich fett darstellen this.doc.setBold(start, end, !this.doc.isBold(start)); } // Kursiv setzen else if (evt.getSource() == this.italic) { // siehe "Fett setzen" this.doc.setItalic(start, end, !this.doc.isItalic(start)); } // Unterstreichen else if (evt.getSource() == this.underline) { // siehe "Fett setzen" this.doc.setUnderline(start, end, !this.doc.isUnderline(start)); } // Schriftfarbe verändern else if (evt.getSource() == this.foreground) { Color col = JColorChooser.showDialog(this, "Schriftfarbe auswählen", Color.BLACK); if (col != null) { this.doc.setForeground(start, end, col); } } // Hintergrundfarbe verändern else if (evt.getSource() == this.background) { Color col = JColorChooser.showDialog(this, "Hintergrundfarbe auswählen", Color.WHITE); if (col != null) { this.doc.setBackground(start, end, col); } } // Bild einfügen else if (evt.getSource() == this.pic) { int retval = this.imch.showOpenDialog(this); if (retval == JFileChooser.APPROVE_OPTION) { this.pane.replaceSelection(""); try { this.doc.setIcon(this.pane.getCaretPosition(), new ImageIcon(this.imch.getSelectedFile().getAbsolutePath())); } catch (BadLocationException ble) { ble.printStackTrace(); } } } } public void itemStateChanged(ItemEvent evt) { // Nur reagieren, falls etwas neues selektiert wurde if (evt.getStateChange() == ItemEvent.SELECTED) { // Start und End Position der Selektion auslesen int start = this.pane.getSelectionStart(); int end = this.pane.getSelectionEnd(); // Falls keine sinnvolle Selektion => kompletten Text bearbeiten if (start >= end) { start = 0; end = this.pane.getText().length(); } // Schriftart setzen if (evt.getSource() == this.fonts) { this.doc.setFont(start, end, this.fonts.getSelectedItem().toString()); } // Schriftgröße setzen else if (evt.getSource() == this.size) { this.doc.setFontSize(start, end, Integer.parseInt(this.size.getSelectedItem().toString())); } } } // Main Methode zum Testen public static void main(String[] args) { JFrame frame = new JFrame("EditorComponent Test"); frame.add(new JScrollPane(new EditorComponent())); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(500, 300); frame.setVisible(true); } }
SUPER, VIELEN DANK!!!
sehr hilfreicher Beitrag!!
Danke! Jens
Vielen lieben Dank für diesen hilfreichen Beitrag, das hat mir wirklich sehr geholfen.
Eine Frage hätte ich aber:
Wie kann ich verwirklichen, dass meine Toolbar buttons für mehrere TextPanes funktionieren?
Ich hab ein wenig mit verschiedenen Listenern rumprobiert, aber als relativer Anfänger komm ich nicht wirklich voran.
Danke im voraus,
Sandra
Hallo Sandra,
so pauschal und ohne Struktur/Grund/Vorhaben/Code zu kennen, ist das schwer beantwortbar. Generell: Das was für ein JTextPane (bzw. dessen Document) gemacht wird, nicht nur für dieses eine sondern für alle machen. Bspw. indem die die TextPanes und Documents in einem Array gehalten werden und über diese bei den jeweiligen Aktionen iteriert wird. Je nach Anforderung ggf. noch ein Flag, welches das Aktive ist und dann nur dieses verändern.
Grüße
Stefan
Hallo Stefan, danke für’s schnelle Antworten.
Ich habe eine JToolBar mit diversen Buttons und JComboboxen für Schriftart und -größe.
Eurer EditorDocument habe ich um Methoden für strikeThrough, superscript, subskript und den alignments left, center, right und justified.
In meinem JPanel habe ich die Toolbar und 2 JTextPanes.
Wenn ich nun ein JTextPane mit dem ActionListener registriere, so wie ihr es auch zeigt, klappt alles wunderbar. Nur möchte ich beide JTextPanes mit der Toolbar beeinflussen können. Einfach das JTextPane nehmen, dass den Fokus hat (mit hasFocus() ), funktioniert nicht, da ich ja, sobald ich den Button klicke, Focus verliere.
Ich weiß einfach nicht wirklich, wie ich es bewerkstelligen soll, dass meine ToolbarButtons auf das zuletzt aktive TextPane reagieren.
Hoffe, diese Beschreibung ist ausreichend.
Grüße,
Sandra
Hallo Sandra,
ich denke die Beschreibung ist ausreichend, ja. Wie wäre es, wenn mit einem FocusListener auf focusLost der jeweiligen JTextPanes gehört wird? Dann einfach in eine Variable das zuletzt fokusierte JTextPane schreiben und darauf beim Buttonklick zugreifen.
Grüße
Stefan
Hallo Stefan,
das ist ne gute Idee, hätte mir einfallen müssen, klingt absolut logisch. Ich habs in der Zwischenzeit nun anders gelöst. Ich hab einfach alle Toolbar Buttons nicht focussierbar gemacht. Somit verliert das aktuelle TextPane seinen Focus nicht mehr. Deine Idee klingt aber nach der eleganteren Lösung, wärend meine zwar auch funktioniert, sich aber ein wenig nach cheaten anfühlt.
Danke nochmal und viele Grüße,
Sandra
Ich weiß dieser Beitrag ist schon etwas alt, aber ich hoffe hier wird man dennoch gehört 🙂
Vielen Dank auch erst mal für diesen wunderschönen Beitrag.
Ich bin ein ziemlicher Leihe in Sachen Javaprogrammierung, dennoch habe ich mir vorgenommen für ein Schulprojekt solch einen Texteditor zu programmieren. Auf der Suche nach Tipps bin ich auf einen Beitrag gestoßen.
Habe mir beide Teile EditorDocument und EditorComponent kopiert und wollte es einfach mal ausprobieren. Keine angst, werde es nicht übernehmen, nur als Hilfestellung benutzen.
Beim ausführen von EditorComponent bekomme ich jedoch folgende Fehlermeldung vom Compiler:
EditorComponent.java:32:11: error: cannot find symbol
private EditorDocument doc = null;
^
symbol: class EditorDocument
location: class EditorComponent
EditorComponent.java:63:20: error: cannot find symbol
this.doc = new EditorDocument();
^
symbol: class EditorDocument
location: class EditorComponent
Note: EditorComponent.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
2 errors
Ich hoffe hier hat jemand Rat.
Grüße
Matthias
Hallo Matthias,
die EditorComponent Klasse kann die EditorDocument Klasse nicht finden. Diese müssen entweder beide im selben Package liegen oder entsprechend importiert werden.
Der Code kann gerne 1 zu 1 übernommen/kopiert werden. Ich habe damit keine Probleme (aber evtl. der Lehrer ;-)).
Grüße
Stefan
Schnelle Antwort, da bin ich aber baff 🙂
Arbeite mit dem JavaEditor, könnte es sein das das Package dadurch nicht erschaffen oder fehlerhaft erschaffen wird?
Habe auch mal das Package geändert in package Texteditor; da sich beide Files in c:\…\Texteditor\ befinden.
Desweiteren habe ich versuch die EditorDocument via import Texteditor.EditorDocument oder import Texteditor.* einzubinden, aber leider ohne erfolg.
mfg
Matthias
So, läuft jetzt, war wohl ein fehler vom JavaEditor. Über cmd mit javac -Xlint konnte ich es endlich kompilieren.
Vielen dank für deine Schnelle Hilfe.
Mit freundlichen Grüßen
Matthias
Super Tutorial erst einmal 🙂
Ich habe das ganze jetzt auch einmal aussprobiert und es funktioniert super 🙂 Jez will ich nur diesen formatierten Text als HTML Code haben, ich habe jez seit mehreren Stunden bei Google gesucht aber nichts gefunden … jetzt wollte ich hier einmal fragen ob jemand eine Lösung für mein Problem kennt 🙂
Mit freundlichen Grüßen
Markus
Ob es dafür vorgefertigte Komponenten gibt, weiß ich nicht. Aber prinzipiell kann man „einfach“ die Formatierungen auslesen und entsprechend HTML-Tags außenrum einfügen.
Grüße
Stefan
Hallo,
Danke erstmal für deinen Einstieg.
Wie ist es Möglich aus dem ganzen, HTML Text zu generieren 🙁
Danke schön
Hallo Marco,
siehe Antwort eins drüber.
Grüße
Stefan