如何正确初始化 JTextPane StyleSheet,使其他 HTML 启用的组件不受该样式的影响?

How does one properly initialize a JTextPane StyleSheet, so no other HTML enabled component is affected by the style?

我正在尝试使用 JTextPane 呈现一些 HTML 并对其应用 CSS 样式表。这意味着我正在使用 HTMLEditorKitStyleSheet 类。我知道所有 HTMLEditorKits 共享相同的默认 StyleSheet 实例,因此如果您更改此默认样式表对象,您将在应用程序级别应用更改(呈现 HTML 的所有组件)。

但在我的示例中,我认为我已经通过基于默认创建自己的 StyleSheet 实例来避免这种情况。然而,这不起作用,如显示的 JTree 所示,它按照仅打算应用于 JTextPane.

的样式表呈现
import java.awt.*;
import javax.swing.*;
import javax.swing.text.html.*;
import javax.swing.tree.*;

public class TextPaneCssSpill extends JFrame {

    private JTextPane textPane;
    private JTree tree;
    private JSplitPane splitPane;

    public TextPaneCssSpill() {
        HTMLEditorKit hed = new HTMLEditorKit();
        StyleSheet defaultStyle = hed.getStyleSheet();
        StyleSheet style = new StyleSheet();
        style.addStyleSheet(defaultStyle);
        style.addRule("body {font-family:\"Monospaced\"; font-size:9px;}");
        style.addRule("i {color:#bababa; font-size:9px;}"); // gray italic
        hed.setStyleSheet(style);

        textPane = new JTextPane();        
        textPane.setEditorKit(hed);
        textPane.setDocument(hed.createDefaultDocument());

        DefaultMutableTreeNode root = new DefaultMutableTreeNode(new MyNode("name", "argument"), true);
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));

        tree = new JTree(root);
        tree.setCellRenderer(new MyNodeTreeRenderer());

        setLayout(new BorderLayout());
        splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, textPane, tree);
        add(splitPane);

        pack();
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TextPaneCssSpill().setVisible(true);
            }
        });
    }

    private static class MyNode {
        private final String name;
        private final String argument;

        public MyNode(String name, String argument) {
            this.name = name;
            this.argument = argument;
        }

        @Override
        public String toString() {
            return name + " " + argument;
        }        
    }

    private static class MyNodeTreeRenderer extends DefaultTreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
            if (value instanceof DefaultMutableTreeNode) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                if (node.getUserObject() instanceof MyNode) {
                    MyNode mynode = (MyNode) node.getUserObject();
                    setText("<html>" + mynode.name + "&nbsp;<i>" + mynode.argument);
                }
            }
            return this;
        }

    }
}

那么如何正确地初始化这些对象,以便 CSS 不会在应用程序中溢出(以便文本窗格根据 CSS 呈现,而树则不会)?

注:上图中红色下划线表示溢出问题,是我后来添加的(不是,不是渲染器)。

我的代码中有问题的部分是调用 HTMLEditorKit.setStyleSheet(style);。这取代了 all HTMLEditorKits 的默认样式表实例,这是我不知道的。

    /**
     * Set the set of styles to be used to render the various
     * HTML elements.  These styles are specified in terms of
     * CSS specifications.  Each document produced by the kit
     * will have a copy of the sheet which it can add the
     * document specific styles to.  By default, the StyleSheet
     * specified is shared by all HTMLEditorKit instances.
     * This should be reimplemented to provide a finer granularity
     * if desired.
     */
    public void setStyleSheet(StyleSheet s) {
        if (s == null) {
            AppContext.getAppContext().remove(DEFAULT_STYLES_KEY);
        } else {
            AppContext.getAppContext().put(DEFAULT_STYLES_KEY, s);
        }
    }

    /**
     * Get the set of styles currently being used to render the
     * HTML elements.  By default the resource specified by
     * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
     * instances.
     */
    public StyleSheet getStyleSheet() {
        AppContext appContext = AppContext.getAppContext();
        StyleSheet defaultStyles = (StyleSheet) appContext.get(DEFAULT_STYLES_KEY);

        if (defaultStyles == null) {
            defaultStyles = new StyleSheet();
            appContext.put(DEFAULT_STYLES_KEY, defaultStyles);
            try {
                InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
                Reader r = new BufferedReader(
                        new InputStreamReader(is, "ISO-8859-1"));
                defaultStyles.loadRules(r, null);
                r.close();
            } catch (Throwable e) {
                // on error we simply have no styles... the html
                // will look mighty wrong but still function.
            }
        }
        return defaultStyles;
    }

所以需要做的是扩展 HTMLEditorKit 使其成为 return 您的样式表而不更改默认值。

import java.awt.*;
import java.io.IOException;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.swing.tree.*;

public class TextPaneCssSpill extends JFrame {

    private JTextPane textPane;
    private JTree tree;
    private JSplitPane splitPane;
    private StyleSheet style;

    public TextPaneCssSpill() {
        MyHTMLEditorKit hed = new MyHTMLEditorKit();
        StyleSheet defaultStyle = hed.getDefaultStyleSheet();
        style = new StyleSheet();
        style.addStyleSheet(defaultStyle);
        style.addRule("body {font-family:\"Monospaced\"; font-size:9px;}");
        style.addRule("i {color:#bababa; font-size:9px;}"); // gray italic
        hed.setStyleSheet(style);

        textPane = new JTextPane();        
        textPane.setEditorKit(hed);
        textPane.setDocument(hed.createDefaultDocument());
        appendHtmlToTextPane("<i>our gray italic text</i>", textPane);

        DefaultMutableTreeNode root = new DefaultMutableTreeNode(new MyNode("name", "argument"), true);
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));
        root.add(new DefaultMutableTreeNode(new MyNode("name", "argument"), false));

        tree = new JTree(root);
        tree.setCellRenderer(new MyNodeTreeRenderer());

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, textPane, tree);
        add(splitPane);

        pack();
        setLocationRelativeTo(null);
    }

    private void appendHtmlToTextPane(String str, JTextPane pane) {
        Document doc = pane.getDocument();
        if (doc != null) {
            if (doc instanceof HTMLDocument) {
                HTMLDocument htmlDoc = (HTMLDocument) doc;
                Element html = htmlDoc.getDefaultRootElement();
                if (HTML.Tag.HTML.toString().equalsIgnoreCase(html.getName())) {
                    Element body = null;
                    for (int i = 0; i < html.getElementCount(); i++) {
                        Element element = html.getElement(i);
                        if (element.getAttributes().getAttribute(StyleConstants.NameAttribute) == HTML.Tag.BODY) {
                            body = element;
                            break;
                        }
                    }
                    if (HTML.Tag.BODY.toString().equalsIgnoreCase(body.getName())) {
                        try {                            
                            htmlDoc.insertBeforeEnd(body, str);
                            Element lastLine = body.getElement(body.getElementCount() - 1);
                            int end = lastLine.getStartOffset();
                            textPane.setCaretPosition(end);

                        } catch (BadLocationException e) {
                        } catch (IOException ex) {
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TextPaneCssSpill().setVisible(true);
            }
        });
    }

    private static class MyNode {
        private final String name;
        private final String argument;

        public MyNode(String name, String argument) {
            this.name = name;
            this.argument = argument;
        }

        @Override
        public String toString() {
            return name + " " + argument;
        }        
    }

    private static class MyNodeTreeRenderer extends DefaultTreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
            if (value instanceof DefaultMutableTreeNode) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                if (node.getUserObject() instanceof MyNode) {
                    MyNode mynode = (MyNode) node.getUserObject();
                    setText("<html>" + mynode.name + "&nbsp;<i>" + mynode.argument);
                }
            }
            return this;
        }

    }

    /**
     * Avoid setting the stylesheet for all HTMLEditorKit instances.
     */
    private static class MyHTMLEditorKit extends HTMLEditorKit {

        private StyleSheet myStyle;

        @Override
        public StyleSheet getStyleSheet() {
            return myStyle == null ? super.getStyleSheet() : myStyle;
        }

        @Override
        public void setStyleSheet(StyleSheet s) {
            this.myStyle = s;
        }

        public StyleSheet getDefaultStyleSheet() {
            return super.getStyleSheet();
        }

        public void setDefaultStyleSheet(StyleSheet s) {
            super.setStyleSheet(s);
        }

    }
}