获取文本控件的输出流

在 Swing 中,文本控件没有输出流!所谓的文本控件这里指派生自 JTextComponent 的控件,如 JTextField、JTextArea。但是,有时我们会有需要提取文本控件的输出流。通过向这个流写入文本,对应的文本控件上就会显示出来。比如,我们想把异常链给打印到控件上。Exception.printStackTrace() 方法只能接受 PrintStream 或 PrintWriter 参数。

既然控件没有流,那我们创建一个流。基于这样的思路:通过向流中写文本,在流的实现中将接收到的文本通过 JTextComponent.setText(String) 的方式写到控件上。一个简单的实现:

package ydcode.swing;

import java.awt.EventQueue;
import java.io.PrintStream;

import javax.swing.text.JTextComponent;

public class TextComponentPrintStream extends PrintStream {

private JTextComponent target;

private TextComponentPrintStream(final JTextComponent target) {
super(System.out);

if (target == null) throw new NullPointerException();

this.target = target;
}

public static TextComponentPrintStream getTextComponentStream(JTextComponent target) {
return new TextComponentPrintStream(target);
}

@Override
public void write(byte[] buf, int off, int len) {
final String msg = new String(buf, off, len);

EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
target.setText(target.getText() + msg);
}

});
}

}

这个实现参考了陈维的[url=http://blog.csdn.net/chenweionline/archive/2008/03/07/2156159.aspx]将标准输出重定向到GUI[/url]。

在 write() 方法中,我们设置控件的文本。第 13 行的 super(System.out) 是为了让基类的 PrintStream 正常工作,不报错。我们可以设置成任意一个流,super(anyStreamYouLike)。因为父类根本就没有机会向 System.out 写入哪怕一个字节。这里我们基于这样一个 PrintStream 的实现细节:所有的 print 和 write 方法最终都是调用 OutputStream.write(byte[] buf, int off, int len) 来实现的。

这么做有点 tricky,甚至有点邪恶。但是如果我们要自己搞一个流出来,这是我目前看到的唯一方法。这么写有一个问题。假设我们正在写一个遍历文件树的程序。每当找到一个文件就把它打印到多行文本框中。遍历的方法在单独的一个线程中执行。也许你认为这样程序就可以正常工作了。但是当文件树包含较多文件时(比如 C:),文本框基本上就不响应了。为什么呢?我们已经用单独的一个线程来进行遍历了,并没有占用主线程,界面是不应该当掉的呀!原因是当向流中写得太频繁时,会导致 setText() 的调用过于频繁而使 UI 界面假死。EventQueue 中充满了 setText() 的调用而使得其它的 UI 事件没有机会得到响应。解决的方法有二。一是在 UI 中调用这个流写数据时用 SwingWorker 类。这个类从 JDK 6 开始有的。二是改装一下这个流的实现,给它加一个缓冲。使得多次对 setText() 的调用合并为一个。下面是实现代码,

package ydcode.swing;

import java.awt.EventQueue;
import java.io.PrintStream;
import java.util.List;

import javax.swing.text.JTextComponent;

import sun.swing.AccumulativeRunnable;

public class TextComponentPrintStream extends PrintStream {

private AccumulativeRunnable<String> doSetText;

private TextComponentPrintStream(final JTextComponent target) {
super(System.out);

doSetText = new AccumulativeRunnable<String>() {

@Override
protected void run(List<String> textQueue) {
if (textQueue.size() > 0) {
StringBuilder sb = new StringBuilder();

sb.append(target.getText());
for (String str : textQueue) {
sb.append(str);
}

target.setText(sb.toString());
}
}

};
}

public static TextComponentPrintStream getTextComponentStream(JTextComponent target) {
return new TextComponentPrintStream(target);
}

@Override
public void write(byte[] buf, int off, int len) {
final String msg = new String(buf, off, len);

EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
doSetText.add(msg);
}

});
}

}

这个实现更加 tricky。它参考了 JDK 中 SwingWorker 的实现。这里面用到了一个关键的类,sun.swing.AccumulativeRunnable。这个类可以在 rt.jar 中找到。你可以在 [url]http://www.google.com/codesearch[/url]中找到它的源码和注释。也可以用 DJ 之类的反编译工具在 rt.jar 中查看它的源码。如果你比较懒,可以在这里看到:[url]http://www.google.com/codesearch/p?hl=en#5nd3vJ4zpWY/src/share/classes/sun/swing/AccumulativeRunnable.java&q=AccumulativeRunnable[/url]。AccumulativeRunnable 可以使得多次对 Runnable 的调用放在一次执行。现在,我假设你正在看它的注释和源码……

Okay,现在你已经明白它的作用了。第一次调用 add() 方法会使得 submit() 被调用。submit() 将 run() 的调用放在了 Swing 的 EventQueue 中去排队列执行。而当 EventQueue 最终执行了 run() 时,这又将导致新一轮的循环。这样,我们就既不会让 EventQueue 太满导致界面假死,同时又能及时地更新 UI 了。

最后介绍一个更有意思的方法。它可以让你控制 setText() 被调用的最快频率。代码如下,

package ydcode.swing;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintStream;
import java.util.List;

import javax.swing.Timer;
import javax.swing.text.JTextComponent;

import sun.swing.AccumulativeRunnable;

public class TextComponentPrintStream extends PrintStream {

private static AccumulativeRunnable<Runnable> doSubmit = new DoSubmitAccumulativeRunnable();

private AccumulativeRunnable<String> doSetText;

private TextComponentPrintStream(final JTextComponent target) {
super(System.out);

doSetText = new AccumulativeRunnable<String>() {

@Override
protected void run(List<String> textQueue) {
if (textQueue.size() > 0) {
StringBuilder sb = new StringBuilder();

sb.append(target.getText());
for (String str : textQueue) {
sb.append(str);
}

target.setText(sb.toString());
}
}

@Override
protected void submit() {
doSubmit.add(this);
}

};
}

public static TextComponentPrintStream getTextComponentStream(JTextComponent target) {
return new TextComponentPrintStream(target);
}

@Override
public void write(byte[] buf, int off, int len) {
final String msg = new String(buf, off, len);

EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
doSetText.add(msg);
}

});
}

private static class DoSubmitAccumulativeRunnable extends
AccumulativeRunnable<Runnable> implements ActionListener {

private final static int DELAY = 1000 / 5;

@Override
protected void run(List<Runnable> tasks) {
for (Runnable task : tasks) {
task.run();
}
}

@Override
protected void submit() {
Timer timer = new Timer(DELAY, this);
timer.setRepeats(false);
timer.start();
}

@Override
public void actionPerformed(ActionEvent e) {
run();
}

}

}

那个 DELAY 用来控制隔多少毫秒调用一次。由于本人表达能力有限,这个就不再解释了。如果你搞明白了 AccumulativeRunnable 的作用,那么理解这个应该不成问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值