重定向System.out和System.err到JTextPane,分别用黑色红色显示
把 System.out 和 System.err 重定向到 JTextArea 的做法在网上能找到不少,由于 JTextArea 不能用不同的字体分别显示内容。但我还是希望能象 Eclipse 控制台那样,标准输出为黑色,错误信息为红色,于是选择了 JTextPane 作为输出目的地。线程之间通信息用到了 PipedInputStream 、PipedOutputStream 和 SwingUtilities.invokeLater(new Runnable()。
自定义了一个 JScrollPane,类名为 ConsolePane,写成的单例类;使用时只需要在你的面板上加上 ConsolePane组件,例如:getContentPane().add(ConsolePane.getInstance(), BorderLayout.CENTER);
界面截图(黑色和红色分别显示 System.out 和 System.err 定向的输出内容):

像控制台那样也设置了最大输出缓冲行数,当超过一定行数后会自动把前面的若干行删除,防止内存占用过大。
Log4J 与 ConsolePane
作为自己应用程序的输出控制台还是不错的。有个问题,如果要捕获 Log4J 的输出必须选择 1.2.13 或以上的版本的 Log4J,并在 log4j.properties 设置
log4j.appender.console.follow = true #沿用 System.setOut() 或 System.setErr() 设置,默认为 false
在 1.2.13 以前的 Log4J 的 ConsoleAppender 中没有 follow 属性,Lo4J 不支持 System.out 和 System.err 的分别输出,你可以在 log4j.peroperties 中设置
lo4j.appender.console.target = System.out #或 System.err,默认为 System.out
Log4J 输出信息到控制台要么全到 System.out,要么全到 System.err,也就是在 ConsolePane 中没法分不同颜色显示 log.error() 和 log.debug() 信息。
这个问题,可以改善的,比如 Eclipse 中就不依赖于 log4j.properties 中怎么设置的。同样在 Eclipse 中也没法让 error 和 debug 信息分不同颜色信息,除非改写 Log4J 的 ConsoleAppender 才能分颜色显示。
下面分别列出 ConsolePane 的实现代码和一个测试代码 TestConsolePane
1. ConsolePane 代码
当前还有一些 Bug 未解决:
1. final int len = pi.read(buf); 开始时会产生 java.io.IOException: Write end dead 异常,好像这还是 JDK 本身的 Bug,但不影响使用
2. 输出时有时会缺几个字母,或产生空行
3. 因为实际工作线程来输出,所以输出顺序有时不能保证
谁有兴趣的话,可以进一步研究一番;可对这个类再做润色,如增加右键菜单,可拷贝、很剪切、清除所有输出、不自动滚动。就像 Eclipse 的控制台那样。
TestConsolePane 代码
参考:1. e988. Implementing a Console Window with a JTextArea Component
2. e989. Inserting Styled Text in a JTextPane Component
3. e1007. Setting the Font and Color of Text in a JTextPane Using Styles
4. JEdit syntax 中类 JEditTextArea 的实现 永久链接 https://yanbin.blog/system-out-system-err-jtextpane/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
自定义了一个 JScrollPane,类名为 ConsolePane,写成的单例类;使用时只需要在你的面板上加上 ConsolePane组件,例如:getContentPane().add(ConsolePane.getInstance(), BorderLayout.CENTER);
界面截图(黑色和红色分别显示 System.out 和 System.err 定向的输出内容):
像控制台那样也设置了最大输出缓冲行数,当超过一定行数后会自动把前面的若干行删除,防止内存占用过大。
Log4J 与 ConsolePane
作为自己应用程序的输出控制台还是不错的。有个问题,如果要捕获 Log4J 的输出必须选择 1.2.13 或以上的版本的 Log4J,并在 log4j.properties 设置
log4j.appender.console.follow = true #沿用 System.setOut() 或 System.setErr() 设置,默认为 false
在 1.2.13 以前的 Log4J 的 ConsoleAppender 中没有 follow 属性,Lo4J 不支持 System.out 和 System.err 的分别输出,你可以在 log4j.peroperties 中设置
lo4j.appender.console.target = System.out #或 System.err,默认为 System.out
Log4J 输出信息到控制台要么全到 System.out,要么全到 System.err,也就是在 ConsolePane 中没法分不同颜色显示 log.error() 和 log.debug() 信息。
这个问题,可以改善的,比如 Eclipse 中就不依赖于 log4j.properties 中怎么设置的。同样在 Eclipse 中也没法让 error 和 debug 信息分不同颜色信息,除非改写 Log4J 的 ConsoleAppender 才能分颜色显示。
下面分别列出 ConsolePane 的实现代码和一个测试代码 TestConsolePane
1. ConsolePane 代码
1package com.unmi;
2
3import java.awt.Color;
4import java.awt.Dimension;
5import java.io.IOException;
6import java.io.PipedInputStream;
7import java.io.PipedOutputStream;
8import java.io.PrintStream;
9
10import javax.swing.JScrollPane;
11import javax.swing.JTextPane;
12import javax.swing.SwingUtilities;
13import javax.swing.text.AbstractDocument;
14import javax.swing.text.BadLocationException;
15import javax.swing.text.Document;
16import javax.swing.text.Element;
17import javax.swing.text.Style;
18import javax.swing.text.StyleConstants;
19import javax.swing.text.StyledDocument;
20
21/**
22 * @author Unmi
23 */
24public class ConsolePane extends JScrollPane {
25 private PipedInputStream piOut;
26 private PipedInputStream piErr;
27 private PipedOutputStream poOut;
28 private PipedOutputStream poErr;
29
30 private JTextPane textPane = new JTextPane();
31
32 private static ConsolePane console = null;
33
34 public static synchronized ConsolePane getInstance() {
35 if (console == null) {
36 console = new ConsolePane();
37 }
38 return console;
39 }
40
41 private ConsolePane() {
42
43 setViewportView(textPane);
44
45 piOut = new PipedInputStream();
46 piErr = new PipedInputStream();
47 try {
48 poOut = new PipedOutputStream(piOut);
49 poErr = new PipedOutputStream(piErr);
50 } catch (IOException e) {
51 }
52
53 // Set up System.out
54 System.setOut(new PrintStream(poOut, true));
55
56 // Set up System.err
57 System.setErr(new PrintStream(poErr, true));
58
59 textPane.setEditable(true);
60 setPreferredSize(new Dimension(640, 120));
61
62 // Create reader threads
63 new ReaderThread(piOut).start();
64 new ReaderThread(piErr).start();
65 }
66
67 /**
68 * Returns the number of lines in the document.
69 */
70 private final int getLineCount() {
71 return textPane.getDocument().getDefaultRootElement().getElementCount();
72 }
73
74 /**
75 * Returns the start offset of the specified line.
76 *
77 * @param line The line
78 * @return The start offset of the specified line, or -1 if the line is
79 * invalid
80 */
81 private int getLineStartOffset(int line) {
82 Element lineElement = textPane.getDocument().getDefaultRootElement()
83 .getElement(line);
84 if (lineElement == null)
85 return -1;
86 else
87 return lineElement.getStartOffset();
88 }
89
90 private void replaceRange(String str, int start, int end) {
91 if (end < start) {
92 throw new IllegalArgumentException("end before start");
93 }
94 Document doc = textPane.getDocument();
95 if (doc != null) {
96 try {
97 if (doc instanceof AbstractDocument) {
98 ((AbstractDocument) doc).replace(start, end - start, str,
99 null);
100 } else {
101 doc.remove(start, end - start);
102 doc.insertString(start, str, null);
103 }
104 } catch (BadLocationException e) {
105 throw new IllegalArgumentException(e.getMessage());
106 }
107 }
108 }
109
110 class ReaderThread extends Thread {
111 private PipedInputStream pi;
112
113 ReaderThread(PipedInputStream pi) {
114 this.pi = pi;
115 }
116
117 public void run() {
118 final byte[] buf = new byte[1024];
119
120 while (true) {
121 try {
122 final int len = pi.read(buf);
123 if (len == -1) {
124 break;
125 }
126 SwingUtilities.invokeLater(new Runnable() {
127 public void run() {
128 try {
129
130 StyledDocument doc = (StyledDocument) textPane
131 .getDocument();
132
133 // Create a style object and then set the style
134 // attributes
135 Style style = doc.addStyle("StyleName", null);
136
137 Color foreground = pi == piOut ? Color.BLACK
138 : Color.RED;
139 // Foreground color
140 StyleConstants.setForeground(style, foreground);
141
142 // Append to document
143 String outstr = new String(buf, 0, len);
144 doc.insertString(doc.getLength(), outstr, style);
145
146 } catch (BadLocationException e) {
147 // e.printStackTrace();
148 }
149
150 // Make sure the last line is always visible
151 textPane.setCaretPosition(textPane.getDocument()
152 .getLength());
153
154 // Keep the text area down to a certain line count
155 int idealLine = 150;
156 int maxExcess = 50;
157
158 int excess = getLineCount() - idealLine;
159 if (excess >= maxExcess) {
160 replaceRange("", 0, getLineStartOffset(excess));
161 }
162 }
163 });
164 } catch (IOException e) {
165 // e.printStackTrace();
166 }
167 }
168 }
169 }
170} 当前还有一些 Bug 未解决:
1. final int len = pi.read(buf); 开始时会产生 java.io.IOException: Write end dead 异常,好像这还是 JDK 本身的 Bug,但不影响使用
2. 输出时有时会缺几个字母,或产生空行
3. 因为实际工作线程来输出,所以输出顺序有时不能保证
谁有兴趣的话,可以进一步研究一番;可对这个类再做润色,如增加右键菜单,可拷贝、很剪切、清除所有输出、不自动滚动。就像 Eclipse 的控制台那样。
TestConsolePane 代码
1package com.unmi;
2
3 import java.awt.BorderLayout;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.util.Date;
7 import java.util.Random;
8
9 import javax.swing.JButton;
10 import javax.swing.JFrame;
11
12 /**
13 * @author Unmi
14 */
15 public class TestConsolePane extends JFrame {
16 public TestConsolePane() {
17
18 setTitle("Redirect System.out and System.error Test Application");
19 setSize(640, 240);
20 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
21
22 getContentPane().add(ConsolePane.getInstance(), BorderLayout.CENTER);
23 JButton button = new JButton("Click Me to Output Message");
24 getContentPane().add(button, BorderLayout.SOUTH);
25
26 button.addActionListener(new ActionListener() {
27 public void actionPerformed(ActionEvent e) {
28 Random random = new Random();
29 int num = random.nextInt(10);
30 String msg = ": Hello Unmi, Redirect "
31 + ((num % 2 == 1) ? "\"System.out\"" : "\"System.err\"")
32 + " to ConsolePane, Today: ";
33 if (num % 2 == 1)
34 System.out.println(num + msg + new Date());
35 else
36 System.err.println(num + msg + new Date());
37 }
38 });
39
40 setVisible(true);
41 }
42
43 public static void main(String[] args) {
44 new TestConsolePane();
45 }
46} 参考:1. e988. Implementing a Console Window with a JTextArea Component
2. e989. Inserting Styled Text in a JTextPane Component
3. e1007. Setting the Font and Color of Text in a JTextPane Using Styles
4. JEdit syntax 中类 JEditTextArea 的实现 永久链接 https://yanbin.blog/system-out-system-err-jtextpane/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。