Aktuelle Zeile in JTextPane farblich hervorheben

preachie

Aktives Mitglied
Hallo zusammen,

ich versuche nun mittlerweile seit fast 2 Tagen eine Anforderung an ein Editorfenster in den Griff zu bekommen... leider bislang vergeblich 🙁

Zielsetzung ist, dass ich ein Text-Editor/Eingabefenster habe, in dem die gesamte, aktuelle Zeile farblich hervorgehoben ist, wie man es von gängigen Texteditoren her kennt.
Mit "aktueller" Zeile ist die Zeile gemeint, in der sich aktuell der Cursor, bzw. besser gesagt der/die/das??? Caret.

Über Google habe ich 2 Ansätze gefunden, bei denen ich es aber leider nicht schaffe, sie korrekt umzusetzen.

Der erste Ansatz verfolgt den Weg den DefaultHighlighter zu überschreiben (Custom Highligth in JEditorPanel
Java:
 [line] [word] [highlight] [jeditorpanel][/url]). Bei dem zweiten Ansatz wird stattdessen der HighlighterPainter überschrieben ([url=http://www.jroller.com/santhosh/date/20050622]Santhosh Kumar's Weblog : <a href="https://myswing.dev.java.net/MyBlog/MySwingTree.html">Santhosh Kumar's Weblog</a>[/url]).

Aktuell versuche ich mich noch immer am ersten Ansatz, der aber wie gesagt nicht so funktioniert wie gewünscht.

Am Ende des Beitrags poste ich dann noch ein kleines Programmbeispiel.
Dieses verhält sich wie folgt:
- Wenn ich das Programm starte, befindet sich der Cursor aktiv in der ersten Zeile des JTextPane. Die Zeile ist jedoch nicht highlighted
- Tippe ich beliebige Zeichen ein, so werden diese farblich hinterlegt, aber auch nur diese, nicht auf gesamter Breite des JTextPane
- Wechsel ich mit Return in die nächste Zeile, ist das Highlighting aus Zeile 1 wieder weg (was so richtig ist) und tippe ich nun in Zeile 2 Zeichen, werden wieder die eingegebenen Zeichen farblich hinterlegt, nur nicht die gesamte Zeile.
- Wechsel ich nun mit dem Caret zurück in Zeile 1, egal ob mit Mausklick oder Cursor up, so wird die Zeile 1 in der gesamten Breite farblich hinterlegt, so wie es von Anfang an sein sollte.

Ich hoffe, mir kann hier jemand sagen, was ich falsch mache, bzw. erklären, wieso es nicht funkionieren kann.
Gern nehme ich auch alternative Lösungsansätze entgegen, wie man die Anforderung noch ganz anders realisieren kann.

Vielen Dank schonmal im Vorfeld für Eure Idee.
Gruß
Preachie

[code=Java]import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;

import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;

public class HighlightProblem extends JFrame {
	private static final long serialVersionUID = 1L;
	private final JTextPane textPane;
	private final Highlighter.HighlightPainter cyanPainter;
	
	public HighlightProblem() {
		cyanPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.CYAN);

		textPane = new JTextPane();
		textPane.setPreferredSize(new Dimension(500, 300));
		textPane.setHighlighter(new LineHighlighter());
		textPane.addCaretListener(new CaretListener() {
			@Override
			public void caretUpdate(CaretEvent e) {
				setHighlight(e);
			}
		});
		getContentPane().add(textPane);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		pack();
		setVisible(true);
	}

	public static void main(String[] args) {
		new HighlightProblem();
	}

	public void setHighlight(CaretEvent e) {
		textPane.getHighlighter().removeAllHighlights();
		int currentLine = getLineFromOffset(textPane, e.getDot());
		int startPos = getLineStartOffsetForLine(textPane, currentLine);
		int endOffset = getLineEndOffsetForLine(textPane, currentLine);
		
		try {
			textPane.getHighlighter().addHighlight(startPos, endOffset, cyanPainter);			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		textPane.repaint();
	}

	public int getLineFromOffset(JTextComponent component, int offset) {
		return component.getDocument().getDefaultRootElement().getElementIndex(offset);
	}

	public int getLineStartOffsetForLine(JTextComponent component, int line) {
		return component.getDocument().getDefaultRootElement().getElement(line).getStartOffset();
	}

	public int getLineEndOffsetForLine(JTextComponent component, int line) {
		return component.getDocument().getDefaultRootElement().getElement(line).getEndOffset();
	}

	public class LineHighlighter extends DefaultHighlighter {
		private JTextComponent component;

		@Override
		public final void install(final JTextComponent c) {
			super.install(c);
			this.component = c;
		}

		@Override
		public final void deinstall(final JTextComponent c) {
			super.deinstall(c);
			this.component = null;
		}

		@Override
		public final void paint(final Graphics g) {
			final Highlighter.Highlight[] highlights = getHighlights();
			final int len = highlights.length;
			for (int i = 0; i < len; i++) {
				Highlighter.Highlight info = highlights[i];
				if (info.getClass().getName().indexOf("LayeredHighlightInfo") > -1) {
					// Avoid allocing unless we need it.
					final Rectangle a = this.component.getBounds();
					final Insets insets = this.component.getInsets();
					a.x = insets.left;
					a.y = insets.top;
					// a.width -= insets.left + insets.right + 100;
					a.height -= insets.top + insets.bottom;
					final Highlighter.HighlightPainter p = info.getPainter();
					p.paint(g, info.getStartOffset(), info.getEndOffset(), a, this.component);
				}
			}
		}
		
		@Override
		public void removeAllHighlights() {
			textPane.repaint(0, 0, textPane.getWidth(), textPane.getHeight());
			super.removeAllHighlights();
		}
	}
}
 
Hallo.
Ich habe -so denke ich- das was du suchst aus einer Kombination von Santhosh Kumar's Weblog
und
How can I highlight the current row in a JTextArea?
hinbekommen (du wirst den Code sicher wiedererkennen 😉 ):
Java:
public final class RowHighlighter implements Highlighter.HighlightPainter
{ 
    private final Color color; 

    public RowHighlighter() { 
        this(new Color(225, 236, 247)); 
    } 

    public RowHighlighter(Color color) { 
        this.color = color; 
    } 
 
    @Override
    public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) { 
        Rectangle alloc = bounds.getBounds(); 
        try { 
            TextUI mapper = c.getUI(); 
            Rectangle p0 = mapper.modelToView(c, offs0); 
            Rectangle p1 = mapper.modelToView(c, offs1); 
 
            g.setColor(color); 
            if(p0.y==p1.y) { 
                Rectangle r = p0.union(p1); 
                g.fillRect(r.x, r.y, r.width, r.height); 
            }
            else { 
                int p0ToMarginWidth = alloc.x+alloc.width-p0.x; 
                g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height); 
                if ((p0.y+p0.height)!=p1.y) { 
                    g.fillRect(alloc.x, p0.y+p0.height, alloc.width, 
                            p1.y-(p0.y+p0.height)); 
                } 
                g.fillRect(alloc.x, p1.y, (p1.x-alloc.x), p1.height); 
            } 
        }
        catch(BadLocationException e) { 
        } 
    } 

} //class RowHighlighter
Hier habe ich "nur" die Wrappermethode
Code:
_paint()
direkt in
Code:
paint()
gemacht, ohne dass er mit den Offsets was macht. Das führte bei mir nur zum falschen Zeichnen.

Danach habe ich den CaretListener aus dem 2. Link entsprechend übernommen:
Java:
    private final class HighlightChanger implements CaretListener {

        private Object highlighterID = null;
        private final Highlighter.HighlightPainter painter;
        
        public HighlightChanger() {
            super();
            painter = new RowHighlighter();
        }
        
        @Override
        public void caretUpdate( CaretEvent event ) {
            final JTextComponent comp = (JTextComponent) event.getSource();
            if (comp != null && highlighterID != null) {
                comp.getHighlighter().removeHighlight(highlighterID);
                highlighterID = null;
            }
                
            int     pos     = comp.getCaretPosition();
            Element element = Utilities.getParagraphElement(comp, pos);
            int     start   = element.getStartOffset();
            int     end     = element.getEndOffset();
            
            try {
                highlighterID = comp.getHighlighter().addHighlight(
                        start, end, painter);
            }
            catch (BadLocationException exc) {
            }
        }
    } //class HighlightChanger
 
Vielen Dank Leute,

nachdem ich mir zunächst die vorgeschlagene Lösung vom Kroko angesehen habe, finde ich die LinePainter Klasse noch deutlich eleganter. Damit klappt es nun wie gewünscht 🙂
 

Zurück
Oben