重定向System.out和System.err到JTextPane,分别用黑色红色显示(改进)
在上一篇 重定向System.out和System.err到JTextPane,分别用黑色红色显示
中讲了如何把 System.out 和 System.err 重定向到 JTextPane 上,并分不同颜色显示,其中用到了 PipedInputStream、
PipedOutputStream。那个例子还是参考的 《The Java Developers Almanac 1.4》,
翻译出来叫做 《Java 开发者年鉴 1.4》,显得多么的权威啊,我当时还真把它看成官方最佳推荐实现了,太迷信了。
可是现在看来,前面那个实现不仅代码繁琐,而且是 Bug 多多。现在重新对上回的 ConsolePane 来个新的更简洁高效的实现。代码如下:
使用方法依旧,是一个 JScrollPane,加上自己的面板上就行:
getContentPane().add(ConsolePane.getInstance(), BorderLayout.CENTER);
界面效果图同前面基本一样:

能解决的问题恰恰就是上面遗留下来的1、2、3:
1. 不再产生 java.io.IOException: Write end dead 异常
2. 输出时不再会缺几个字母,或产生空行了
3. 输出顺序根据程序执行先后能得到保证
如果希望能捕获到 Log4j 的输出,仍然依赖于要用 Log4j 1.2.13 或以上的版本,并设置属性:
log4j.appender.console.follow = true
这个问题在上一篇 重定向System.out和System.err到JTextPane,分别用黑色红色显示 Log4J 与 ConsolePane 一节中用讲,但是如果是用 SWT 的话,这个问题可以解决,不再依赖于这一属性设置了。
这段时间正在研究 SWT,感觉用起来比 SWING 舒服多了,考虑暂时放耽下 SWING 了,过阵会再写一篇关于把 System.out 和 System.err 定向到 SWT 文本控件的日志。
TestConsolePane 代码还是请见上篇日志:重定向System.out和System.err到JTextPane,分别用黑色红色显示 永久链接 https://yanbin.blog/system-out-system-err-to-jtextpane-improved/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
可是现在看来,前面那个实现不仅代码繁琐,而且是 Bug 多多。现在重新对上回的 ConsolePane 来个新的更简洁高效的实现。代码如下:
1package com.unmi;
2
3import java.awt.*;
4import java.io.*;
5
6import javax.swing.*;
7import javax.swing.text.*;
8
9/**
10 * @author Unmi
11 */
12public class ConsolePane extends JScrollPane {
13
14 private JTextPane textPane = new JTextPane();
15
16 private static ConsolePane console = null;
17
18 public static synchronized ConsolePane getInstance() {
19 if (console == null) {
20 console = new ConsolePane();
21 }
22 return console;
23 }
24
25 private ConsolePane() {
26
27 setViewportView(textPane);
28
29 // Set up System.out
30 PrintStream mySystemOut = new MyPrintStream(System.out, Color.BLACK);
31 System.setOut(mySystemOut);
32
33 // Set up System.err
34 PrintStream mySystemErr = new MyPrintStream(System.err, Color.RED);
35 System.setErr(mySystemErr);
36
37 textPane.setEditable(true);
38 setPreferredSize(new Dimension(640, 120));
39 }
40
41 /**
42 * Returns the number of lines in the document.
43 */
44 private final int getLineCount() {
45 return textPane.getDocument().getDefaultRootElement().getElementCount();
46 }
47
48 /**
49 * Returns the start offset of the specified line.
50 * @param line The line
51 * @return The start offset of the specified line, or -1 if the line is
52 * invalid
53 */
54 private int getLineStartOffset(int line) {
55 Element lineElement = textPane.getDocument().getDefaultRootElement()
56 .getElement(line);
57 if (lineElement == null)
58 return -1;
59 else
60 return lineElement.getStartOffset();
61 }
62
63 /**
64 * 清除超过行数时前面多出行的字符
65 */
66 private void replaceRange(String str, int start, int end) {
67 if (end < start) {
68 throw new IllegalArgumentException("end before start");
69 }
70 Document doc = textPane.getDocument();
71 if (doc != null) {
72 try {
73 if (doc instanceof AbstractDocument) {
74 ((AbstractDocument) doc).replace(start, end - start, str,
75 null);
76 } else {
77 doc.remove(start, end - start);
78 doc.insertString(start, str, null);
79 }
80 } catch (BadLocationException e) {
81 throw new IllegalArgumentException(e.getMessage());
82 }
83 }
84 }
85
86 class MyPrintStream extends PrintStream {
87
88 private Color foreground; //输出时所用字体颜色
89
90 /**
91 * 构造自己的 PrintStream
92 * @param out 可传入 System.out 或 System.err, 实际不起作用
93 * @param foreground 显示字体颜色
94 */
95 MyPrintStream(OutputStream out,Color foreground) {
96 super(out,true); //使用自动刷新
97 this.foreground = foreground;
98 }
99
100 /**
101 * 在这里重截,所有的打印方法都要调用最底一层的方法
102 */
103 public void write(byte[] buf, int off, int len) {
104 final String message = new String(buf, off, len);
105
106 /** SWING非界面线程访问组件的方式 */
107 SwingUtilities.invokeLater(new Runnable() {
108 public void run() {
109 try {
110
111 StyledDocument doc = (StyledDocument) textPane
112 .getDocument();
113
114 // Create a style object and then set the style
115 // attributes
116 Style style = doc.addStyle("StyleName", null);
117
118 // Foreground color
119 StyleConstants.setForeground(style, foreground);
120
121 doc.insertString(doc.getLength(), message, style);
122
123 } catch (BadLocationException e) {
124 // e.printStackTrace();
125 }
126
127 // Make sure the last line is always visible
128 textPane.setCaretPosition(textPane.getDocument()
129 .getLength());
130
131 // Keep the text area down to a certain line count
132 int idealLine = 150;
133 int maxExcess = 50;
134
135 int excess = getLineCount() - idealLine;
136 if (excess >= maxExcess) {
137 replaceRange("", 0, getLineStartOffset(excess));
138 }
139 }
140 });
141 }
142 }
143}使用方法依旧,是一个 JScrollPane,加上自己的面板上就行:
getContentPane().add(ConsolePane.getInstance(), BorderLayout.CENTER);
界面效果图同前面基本一样:

能解决的问题恰恰就是上面遗留下来的1、2、3:
1. 不再产生 java.io.IOException: Write end dead 异常
2. 输出时不再会缺几个字母,或产生空行了
3. 输出顺序根据程序执行先后能得到保证
如果希望能捕获到 Log4j 的输出,仍然依赖于要用 Log4j 1.2.13 或以上的版本,并设置属性:
log4j.appender.console.follow = true
这个问题在上一篇 重定向System.out和System.err到JTextPane,分别用黑色红色显示 Log4J 与 ConsolePane 一节中用讲,但是如果是用 SWT 的话,这个问题可以解决,不再依赖于这一属性设置了。
这段时间正在研究 SWT,感觉用起来比 SWING 舒服多了,考虑暂时放耽下 SWING 了,过阵会再写一篇关于把 System.out 和 System.err 定向到 SWT 文本控件的日志。
TestConsolePane 代码还是请见上篇日志:重定向System.out和System.err到JTextPane,分别用黑色红色显示 永久链接 https://yanbin.blog/system-out-system-err-to-jtextpane-improved/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。