`
yangdong
  • 浏览: 66631 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

获取文本控件的输出流

    博客分类:
  • Java
阅读更多
在 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);
			}
			
		});
	}
	
}

这个实现参考了陈维的将标准输出重定向到GUI

在 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 中找到。你可以在 http://www.google.com/codesearch中找到它的源码和注释。也可以用 DJ 之类的反编译工具在 rt.jar 中查看它的源码。如果你比较懒,可以在这里看到:http://www.google.com/codesearch/p?hl=en#5nd3vJ4zpWY/src/share/classes/sun/swing/AccumulativeRunnable.java&q=AccumulativeRunnable。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 的作用,那么理解这个应该不成问题。
2
0
分享到:
评论

相关推荐

    易语言API扩展文本输出

    在易语言中,我们通常会先通过`GetDC`函数获取窗口或控件的设备上下文,然后使用`ExtTextOut`进行文本输出。 `GetDC`函数用于获取窗口、控件或者指定区域的设备上下文句柄。设备上下文是Windows GDI中用于图形操作...

    易语言源码取窗口内所有控件标题.7z

    6. 输出与显示:获取到控件标题后,程序可能将这些信息输出到控制台、写入文件或者显示在窗口界面上,这涉及易语言的输出函数或者界面组件的使用。 7. 软件调试与测试:编写完代码后,开发者会进行调试和测试,确保...

    C#各类控件的输入输出(思维导图、知识点分析、案例解析) c#经典案例.pdf

    C#各类控件的输入输出(思维导图、知识点分析、案例解析) C#控件是编程语言C#中的一个重要组件,用于在Windows Forms应用程序中创建图形用户界面。控件是用户界面的基本组成部分,提供了与用户交互的方式。C#控件...

    获取文本行数函数

    因此,Windows API提供了`EM_GETLINECOUNT`消息,专门用于获取文本控件中的行数。此消息被发送到文本控件的句柄,返回当前显示的行数。 ### 知识点三:示例代码解析 在给定的代码片段中,首先定义了两个常量`WM_...

    windows 文本输出

    1. `CreateWindowEx` 和 `CreateWindow` 函数:用于创建窗口,其中可以包含文本输出元素,如静态文本控件(`STATIC`)。 2. `SetWindowTextA`, `SetWindowTextW`: 用于设置窗口或控件的文本内容,与`WriteConsole`...

    遍历窗体中的所有控件

    如果找到匹配的窗体,程序将遍历该窗体的所有控件,并将控件的信息(如类型、位置、大小等)输出到MEMO框(通常是用来显示多行文本的控件)中。 遍历控件的过程可以分为以下几个步骤: 1. **枚举窗体**:使用...

    VC控件背景色的变化和获取HID设备属性

    同时,将设备的信息显示在界面上,例如在一个文本控件或者对话框中。 总的来说,本示例涉及了MFC中控件背景色的动态变化以及与HID USB设备的通讯,包括设备属性的获取。通过结合这些技术,你可以创建一个交互式的...

    IE文本转语音控件,web语音(TTS)控件

    这些控件可以在用户浏览网页时,与服务器进行通信,获取并处理文本内容,然后通过浏览器的音频API播放生成的语音。 【标签】中的“B/S语音”指的是这种基于浏览器的语音服务模式,它与传统的C/S(客户端/服务器)...

    MFC 改变静态文本字体大小

    在上面的代码中,我们使用GetDlgItem函数获取ID为IDC_STATIC的静态文本控件,然后使用SetFont函数将cfont1字体对象应用于该控件中。 结论 通过上面的代码,我们可以在MFC对话框中修改静态文本控件的字体大小。通过...

    MFC 记录操作日志,使用Edit控件

    它首先获取Edit控件当前的文本内容,然后将新的日志信息追加到末尾。为了防止日志过长,我们还可以设置一个最大长度,并在达到这个长度时重置文本。 在需要记录日志的地方,调用`AppendToLog`函数即可。例如,在一...

    c#读取listview控件内容

    利用Windows API函数(如`FindWindow`和`FindWindowEx`)找到ListView控件,然后调用`SendMessage`或`SendNotifyMessage`发送消息,如`LVM_GETITEMCOUNT`获取项数,`LVM_GETITEMTEXT`获取项的文本等。 以下是一个...

    kettle常见控件(输入、输出,字段选择、设置变量、记录集连接、值映射、字符串替换、js、Java)的使用案例

    - **文本文件输出**:在处理文本数据时,可以使用“字符串替换”步骤对字段内容进行替换,以满足输出格式要求。 8. JavaScript 和 Java 脚本: - **JavaScript步骤**:允许用户编写自定义的JavaScript代码进行...

    淘晶驰串口屏控件详解

    1. **文本控件**:最基本的元素,用于显示静态或动态的文字信息。可以调整字体、大小、颜色,并设置自动换行。 2. **按钮控件**:用户点击后触发事件,常用于执行特定操作。可自定义文字、背景色、边框样式等。 3....

    重载静态文本控件CStatic,支持背景贴图和透明,v1.1

    重载静态文本控件,功能如下 //功能:设置字体大小 //输入:lfHeight字体大小,单位为像素,不能为负数,当为0时表示采用系统默认大小 //输出:无 //注:内部已经刷新 //王彬 20120710 void SetTextHeight(LONG...

    重载静态文本控件CStatic,支持背景贴图和透明,v1.2

    重载静态文本控件,如果需要新的功能,请获取作者邮箱,发邮件给我,我将第一时间为您添加接口 //功能:获取作者邮箱 //输入:无 //输出:无 //返回:作者信息 //注:内部已经刷新 CString GetAutherEmail(); ...

    MFC 在光标所在位置输出字符串

    获取到句柄后,我们可以使用FindWindowEx或者GetDlgItem等函数进一步定位到光标所在的控件,比如编辑框(CEdit)。 接下来,我们要在该控件的文本末尾或光标位置插入字符串。对于CEdit控件,我们可以使用EM_SETSEL...

    大彩串口屏各组态控件详细介绍

    2.文本控件用户预先通过上位机将文本在画面中的显示坐标、颜色、字体和背景色设置好,这些文本属性会随着图片一起下载到屏幕配置文件中,然后用户单片机只需对相应的文本ID发送字符数据即可完成显示。3.动画控件实现...

    【转】C#各类控件的输入输出(思维导图、知识点分析、案例解析) c#经典案例.pdf

    C#控件的输入输出详解 本文旨在介绍C#中各种控件的输入输出机制,包括DataGridView控件、TextBox控件、RichTextBox控件、MaskedTextBox控件等,并对每种控件的特点和使用方法进行了详细的介绍。 一、DataGridView...

    MFC中调用控制台CMD进行输出两种方法

    使用`_open_osfhandle`函数获取标准输出流(如`stdout`)的文件句柄。 3. **打开文件流**: 使用`_fdopen`函数根据文件句柄打开一个文件流。 4. **重定向输出**: 使用`*stdout = *fp;`语句将标准输出流...

    一文讲清Python PyQt5的控件如何实现拖放获取文件路径(markdown)

    本篇文章将详细讲解如何使用PyQt5的控件,特别是QLineEdit,来实现拖放操作以获取文件路径。我们将深入理解相关代码,并了解如何将这一功能整合到你的项目中。 首先,让我们了解一下QLineEdit。QLineEdit是PyQt5中...

Global site tag (gtag.js) - Google Analytics