[英]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.