简体   繁体   English

在多线程Swing程序中频繁调用setText()

[英]Frequent calls to setText() in multithreaded Swing program

I have a Swing program where work is continuously being done in a non-Swing thread. 我有一个Swing程序,该程序在非SWING线程中连续进行工作。 It often needs to update a JTextPane -- frequently many times per second. 它经常需要更新JTextPane-每秒频繁地更新多次。 I realize that setText() needs to be called from back inside the event-dispatching thread, but I cant figure out how to make this happen smoothly. 我意识到需要从事件调度线程内部重新调用setText(),但是我无法弄清楚如何使它顺利进行。

The following minimal complete example is as close as I've been able to get it, using a PipedInputStream/PipedOutputStream pair, but this only seems to update the screen once every second or so. 以下最小的完整示例已使用PipedInputStream / PipedOutputStream对尽可能接近,但似乎只每秒更新一次屏幕。 I'm not sure what's taking so long. 我不确定花了这么长时间。

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

public class TextTest extends JFrame {
    private JTextPane out = new JTextPane();
    private PipedInputStream pIn = new PipedInputStream();
    private PrintWriter pOut;

    public TextTest() {
        try {
            pOut = new PrintWriter(new PipedOutputStream(pIn));
        }
        catch (IOException e) {System.err.println("can't init stream");}

        add(new JScrollPane(out));
        setSize(500, 300);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);

        // Start a loop to print to the stream continuously
        new Thread() {
            public void run() {
                for (int i = 0; true; i++) {
                    pOut.println(i);
                }
            }
        }.start();

        // Start a timer to display the text in the stream every 10 ms
        new Timer(10, new ActionListener() {
            public void actionPerformed (ActionEvent evt) {
                try {
                    if (pIn.available() > 0) {
                        byte[] buffer = new byte[pIn.available()];
                        pIn.read(buffer);
                        out.setText(out.getText() + new String(buffer));
                    }
                }
                catch (IOException e) {System.err.println("can't read stream");}
            }
        }).start();
    }

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

Am I implementing this wrong? 我执行这个错误吗? Do I just have totally the wrong idea about how to continuously update a JTextPane from outside the EDT? 我是否对从EDT外部连续更新JTextPane完全有错误的想法?

The setText() "method is thread safe, although most Swing methods are not. Please see How to Use Threads for more information." setText()方法是线程安全的,尽管大多数Swing方法都不安全。有关更多信息,请参见如何使用线程

Addendum: For reference, here's some other approaches to updating on the EDT. 附录:作为参考,以下是在EDT上进行更新的其他一些方法 Another thing to note is that the action event handler for javax.swing.Timer executes on the EDT. 要注意的另一件事是javax.swing.Timer的动作事件处理程序在EDT上执行。 Here's my variation: 这是我的变化形式:

import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import javax.swing.text.DefaultCaret;

public class TextTest extends JFrame {

    private JTextArea out = new JTextArea();
    private PipedInputStream pIn = new PipedInputStream();
    private PrintWriter pOut;

    public TextTest() {
        try {
            pOut = new PrintWriter(new PipedOutputStream(pIn));
        } catch (IOException e) {
            System.err.println("can't init stream");
        }

        DefaultCaret caret = (DefaultCaret) out.getCaret();
        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);

        add(new JScrollPane(out));
        setSize(300, 500);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);

        // Start a loop to print to the stream continuously
        new Thread() {

            public void run() {
                for (int i = 0; true; i++) {
                    pOut.println(i);
                }
            }
        }.start();

        // Start a timer to display the text in the stream every 10 ms
        new Timer(10, new ActionListener() {

            public void actionPerformed(ActionEvent evt) {
                try {
                    out.append(String.valueOf((char) pIn.read()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

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

you need to flush the output of your printWriter and i'd suggest a small pause in your thread given its a tight for loop to let the update thread kick in once in a while. 您需要刷新printWriter的输出,鉴于其紧密的for循环,我建议您在线程中稍作暂停,以使更新线程偶尔启动。

 pOut.println(i); 
 pOut.flush();
 try {
      sleep(10);
 } catch (InterruptedException e) {
 }

This will give a smoother flow. 这样可以使流程更流畅。

but this only seems to update the screen once every second or so. 但这似乎只能每秒更新一次屏幕。 I'm not sure what's taking so long. 我不确定花了这么长时间。

System.out.println(pIn.available());

I added the above statement to the actionPerformed code of the Timer. 我将上面的语句添加到Timer的actionPerformed代码中。 Nothing happens until the buffer reaches 1024 bytes. 缓冲区达到1024字节之前,什么都不会发生。 So somehow I guess you need to change the buffer size. 所以我想您需要更改缓冲区大小。

Also, you should not be using setText(). 另外,您不应该使用setText()。 It is inefficient to recreate the Document every time you make a change. 每次进行更改时,重新创建文档的效率都很低。

You could use: 您可以使用:

out.replaceSelection(new String(buffer) );

Or the more common approach is to use: 或更常见的方法是使用:

Document doc = textPane.getDocument();
doc.insertString("...", doc.getLength(), null);

Don't think the insertString() method is thread safe, but the replaceSelection() method is. 不要认为insertString()方法是线程安全的,但是replaceSelection()方法是安全的。

Edit: 编辑:

Just tried playing with a buffer size of 10 in the input stream and flushing the ouput stream and it didn't make any difference, so I guess I don't understand piped streams. 只是尝试在输入流中使用10个缓冲区大小并刷新输出流,这没有任何区别,所以我想我不理解管道流。

The proper tutorial link for concurrency and Swing looks like it's here: Lesson: Concurrency in Swing 并发和Swing的正确教程链接如下所示: 课程:Swing中的并发

@camickr: setText does not create a new document, it effectively does either this: @camickr: setText不会创建新文档,它可以有效地做到这一点:

doc.replace(0, doc.getLength(), s, null);

or this: 或这个:

doc.remove(0, doc.getLength());
doc.insertString(0, s, null);

I'm not claiming that it's efficient however... 我并不是说它是有效的...

Another thing that setText does not do is to cause revalidate() and repaint() to be issued ( setDocument does, however). setText 不能做的另一件事是导致发出revalidate()repaint() (但是setDocument可以)。 It's probably worthwhile to add those two calls after the call to setText . 在调用setText之后添加这两个调用可能是值得的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM