views:

578

answers:

2

Hi, I've got a couple thousand lines of code somewhere and I've noticed that my JTextPane flickers when I update it too much.. I wrote a simplified version here:

import java.awt.*;
import javax.swing.*;

public class Test
{
    static JFrame f;
    static JTextPane a;
    static final String NL = "\n";

    public static void main(String... args)
    {
     EventQueue.invokeLater(new Runnable(){
     public void run()
     {
     f = new JFrame();
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     f.setVisible(true);
     f.setSize(400, 300);
     f.setLocationRelativeTo(null);

     a = new JTextPane();
     f.add(new JScrollPane(a));

     new Thread(new Runnable(){
      public void run()
      {
       int i = 0;
       StringBuffer b = new StringBuffer();
       while(true)
       {
        b.append(++i+NL);
        a.setText(b.toString());
        a.setCaretPosition(b.length());
        try{Thread.sleep(10);}catch(Exception e){}
       }
      }
     }).start();
     }
     });

    }
}

This is for a terminal (cmd) style GUI component--

I think I've made all the optimizations I could here, including having \n as a final variable so it won't be constructed hundreds of times. Still, the flickering is noticeable and unacceptable. After a few minutes, the component freezes completely. I must update the component very quickly, and the pane must be scrolled to the bottom when updated.

I've been thinking about making my own version of JTextPane from scratch, but I'd like to see if you guys have an easier solution.

Thanks.

+1  A: 

Not sure if this will work, but you could try using the insertString() method of the text pane's Document instance. I would try having a single space at the end of the document and keeping the caret positioned after that space; but when you insert a string, insert it before the space. That way the caret position will remain at the end of the document automatically.

I'm thinking that the text pane might be getting redrawn twice, once when you call setText() and once when you call setCaretPosition(), and that might be contributing to the flickering. Not sure, though (it's been a while since I worked with Swing).

David Zaslavsky
+4  A: 

Part of your error is that you are accessing a Swing component from outside the event thread! Yes, setText() is thread-safe, but Swing methods are not Thread-safe unless they are explicitly declared as such. Thus, setCaretPosition() is not Thread-safe and must be accessed from the event thread. This is almost certainly why your application eventually freezes.

NOTE: JTextPane inherits its setText() method from JEditorPane and its setCaretPosition method from JTextComponent, which explains the links in the previous paragraph not going to the JTextPane JavaDoc page.

To be Thread-safe, you really need to at least call setCaretPosition() from within the event thread, which you can do with code like this:

SwingUtilities.invokeAndWait(new Runnable() {
  public void run() {
    a.setText(b.toString());
    a.setCaretPosition(b.length());
  }
}

And since you have to call setCaretPosition() from within the event thread, you might as well also call setText() from the same place.

It's possible that you may not need to manually set the caret position. Check out the section "Caret Changes" in the JavaDoc for JTextComponent.

Finally, you may want to check out a series of two articles:

Eddie
I can't really put all that in the EDT, because the sleep() will clog it up so much that the component will freeze completely.
Jason Gin
Not the sleep! But the setCaretPosition *must* be in the EDT. Otherwise you will experience occasional (or not so occasional) hangs.
Eddie