Swing JTextPane in JScrollPane wird nicht ordentlich neugezeichnet

xehpuk

Top Contributor
Moin, Leute!

Ich möchte eine JTextPane mit Farbverlauf als Hintergrund haben. Dafür habe ich die Tipps hier befolgt.
Die JTextPane soll sich in einem JScrollPane befinden. Da bekam ich nun das Problem, dass die GradientPaint immer weiter in die Länge gezogen wurde, je mehr Zeilen der Text bekam. Sie soll jedoch "statisch" an einer Stelle bleiben, nämlich vom Anfang bis zum Ende des sichtbaren Bereichs des JTextPane gehen. Daher zeichne ich anhand des sichtbaren Bereichs des JTextPane neu.

Dafür nun folgender lauffähiger Code:
Java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

public class GradientTextPane extends JTextPane {
	@Override
	protected void paintComponent(Graphics g) {
		final boolean opaque = isOpaque();
		if (opaque) {
			final Graphics2D g2d = (Graphics2D) g.create();
			final Rectangle size = getVisibleRect();
			final int x1 = size.x;
			final int y1 = size.y;
			final int x2 = size.width + x1;
			final int y2 = size.height + y1;
			final GradientPaint gp = new GradientPaint(0, y1, Color.WHITE, 0, y2, Color.BLACK);
			System.out.println(x1 + "\t" + y1 + "\t" + x2 + "\t" + y2);
			g2d.setPaint(gp);
			g2d.fillRect(x1, y1, x2, y2);
			// Debugging: Rotes X kennzeichnet sichtbaren Bereich
			g2d.setColor(Color.RED);
			g2d.drawLine(x1, y1, x2, y2);
			g2d.drawLine(x2, y1, x1, y2);
			g2d.dispose();
			setOpaque(false);
		}
		super.paintComponent(g);
		setOpaque(opaque);
	}
	
	public static void main(String[] args) {
		final JPanel contentPane = new JPanel(new BorderLayout());
		final GradientTextPane textPane = new GradientTextPane();
		textPane.setEditable(false);
		final JScrollPane scrollPane = new JScrollPane(textPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		contentPane.add(scrollPane, BorderLayout.CENTER);
		final JTextField textField = new JTextField();
		textField.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				final String message = textField.getText().trim();
				if (!message.isEmpty()) {
					final Document document = textPane.getDocument();
					try {
						document.insertString(document.getLength(), message + System.getProperty("line.separator"), null);
						textField.setText("");
					} catch (BadLocationException e1) {
						e1.printStackTrace();
						JOptionPane.showMessageDialog(contentPane, e1.getStackTrace(), e1.toString(), JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		});
		contentPane.add(textField, BorderLayout.SOUTH);
		final JFrame frame = new JFrame("GradientTextPane");
		frame.setContentPane(contentPane);
		frame.setSize(250, 150);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.setVisible(true);
		textField.requestFocusInWindow();
	}
}
Ändere ich die Größe des JFrame, so wird der Farbverlauf auch ordentlich neugezeichnet. Wird jedoch die ScrollBar bewegt, hinterlässt dies "Schlieren", obwohl die JTextPane komplett neugezeichnet wird. Die in der Konsole stehenden Maße des sichtbaren Bereichs sehen ebenfalls korrekt aus.

Wo liegt der Fehler?
 
der Fehler liegt darin, dass du keinen AdjustmentListener implementiert hast.

[java=50] scrollPane.getVerticalScrollBar().addAdjustmentListener(
new AdjustmentListener() {

@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
textPane.repaint();

}
});
[/code]
 
Danke für den Vorschlag.

Die Lösung ist aber leider nicht perfekt, weil es dort noch ab und zu flackert.

Das Problem liegt wohl darin, dass aus Performancegründen nicht alles neu gezeichnet wird, sondern der bisher sichtbare Bereich des JTextPane nach oben bzw. unten kopiert wird und nur der Bereich neu berechnet wird, der vorher nicht zu sehen war.

Ich habe schon versucht, den Clip des Graphics-Objekts zu ändern, was aber keinen Erfolg brachte und zudem sowieso nicht besonders cool ist, da so immer alles neu gezeichnet würde, obwohl etwa nur der Cursor neu gezeichnet werden soll.
 
Ich habe die Lösung gefunden. Man muss den Hintergrund im Viewport des JScrollPane zeichnen.

Dafür erstellt man sich einen anonymen JViewport und überschreibt dort die [c]paintComponent(Graphics)[/c]. Dann fügt man ihm die nicht opaque JTextPane hinzu und setzt ihn schließlich über [c]JScrollPane.setViewport(JViewport)[/c].
 

Zurück
Oben