如何正确地初始化JTextPane样式表,从而不影响其他支持HTML的组件?

时间:2023-02-09 16:01:24

I'm trying to use a JTextPane to render some HTML and apply a CSS stylesheet to it. This means I'm using HTMLEditorKit and StyleSheet classes. I know that all HTMLEditorKits share the same default StyleSheet instance, so if you change this default stylesheet object, you are applying changes at application level (all components that render HTML).

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

But in my example I thought that I had avoided this by creating my own StyleSheet instance based on the default. This does not work however, as evident on the displayed JTree, which renders as per the stylesheet that was only intended to be applied to the JTextPane.

但是在我的示例中,我认为我通过创建基于默认的样式表实例来避免了这一点。但是,这在显示的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;
        }

    }
}

如何正确地初始化JTextPane样式表,从而不影响其他支持HTML的组件?

So how does one properly initialize these objects, so that there is no CSS spillage across the application (so that text pane renders according to CSS, yet the tree does not)?

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

Note: the red underline in the above image indicates the spillage problem and was added by me later (no, it is not the renderer).

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

1 个解决方案

#1


4  

The problematic part of my code is calling HTMLEditorKit.setStyleSheet(style);. This replaces the default stylesheet instance for all HTMLEditorKits, which I was not aware of.

我代码中有问题的部分是调用HTMLEditorKit.setStyleSheet(样式);;这将替换所有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;
    }

So what needs to be done is to extend HTMLEditorKit to make it return your stylesheet without changing defaults.

因此需要做的是扩展HTMLEditorKit,使它返回您的样式表而不更改默认值。

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);
        }

    }
}

如何正确地初始化JTextPane样式表,从而不影响其他支持HTML的组件?

#1


4  

The problematic part of my code is calling HTMLEditorKit.setStyleSheet(style);. This replaces the default stylesheet instance for all HTMLEditorKits, which I was not aware of.

我代码中有问题的部分是调用HTMLEditorKit.setStyleSheet(样式);;这将替换所有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;
    }

So what needs to be done is to extend HTMLEditorKit to make it return your stylesheet without changing defaults.

因此需要做的是扩展HTMLEditorKit,使它返回您的样式表而不更改默认值。

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);
        }

    }
}

如何正确地初始化JTextPane样式表,从而不影响其他支持HTML的组件?