重定向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 来个新的更简洁高效的实现。代码如下:
  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) 进行许可。