【问题标题】:How to change the color of specific words in a JTextPane?如何更改 JTextPane 中特定单词的颜色?
【发布时间】:2013-01-02 06:47:12
【问题描述】:

如何在用户输入时更改JTextPane 中特定单词的颜色? 我应该覆盖JTextPane paintComponent 方法吗?

【问题讨论】:

标签: java swing jtextpane


【解决方案1】:

@康斯坦丁

亲爱的康斯坦丁, 我在我的小项目中使用了您的优秀解决方案,经过几次调整后,您的解决方案对我来说效果很好。

如果您允许,我的更改是:

我在自己的 JFrame 中使用你的 Class KeywordStyledDocument:

StyleContext styleContext = new StyleContext();
Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE);

这一行我已经改变: MutableAttributeSet cwStyle = Functions.style(true, false, Color.RED);

private JTextPane jTextPaneNumbers = new JTextPane(new KeywordStyledDocument(defaultStyle, cwStyle));

我在一个名为 style 的静态函数中外包了 cwStyle 实例的供应:

public static MutableAttributeSet style(boolean boldness, boolean italic, Color color) {
    
    MutableAttributeSet s = new SimpleAttributeSet();
    
    StyleConstants.setLineSpacing(s, -0.2f);
    StyleConstants.setBold(s, boldness);
    StyleConstants.setItalic(s, italic);
    StyleConstants.setForeground(s, color);
    
    return s;
}

此外,正如您在上面看到的,cwStyle 类不再是 StyleConstants 的实例,而是 MutableAttributeSet 的 Inctance。因此,我自然也必须更改 KeywordStyledDocumentClass 的构造函数:

public KeywordStyledDocument(Style defaultStyle, MutableAttributeSet cwStyle) {
    _defaultStyle =  defaultStyle;
    _cwStyle = cwStyle;
}

在这个小改动并在您的 isReservedWord 函数中添加我自己的“单词”并将我的 Characters ' 和 * 添加到您的 processWord 函数之后:

 ...word.toUpperCase().trim().equals("UNION") ||
                    word.toUpperCase().trim().equals("UNIQUE") ||
                    word.toUpperCase().trim().equals("WHERE") ||
                    word.trim().equals("''''''") ||
                    word.trim().equals("******") 
                    );
 if(!(Character.isLetter(ch) || Character.isDigit(ch) || ch == '_' || ch == '\'' || ch == '*')) {

我成了我心愿的结果:

非常感谢您在这里展示您的代码。

【讨论】:

    【解决方案2】:

    您可以像我在此处为我正在构建的带有关键字文本着色的 SQL 编辑器所做的那样扩展 DefaultStyledDocument ...

        import java.util.ArrayList;
        import java.util.List;
        import javax.swing.text.AttributeSet;
        import javax.swing.text.BadLocationException;
        import javax.swing.text.DefaultStyledDocument;
        import javax.swing.text.Style;
    
        public class KeywordStyledDocument extends DefaultStyledDocument  {
            private static final long serialVersionUID = 1L;
            private Style _defaultStyle;
            private Style _cwStyle;
    
            public KeywordStyledDocument(Style defaultStyle, Style cwStyle) {
                _defaultStyle =  defaultStyle;
                _cwStyle = cwStyle;
            }
    
             public void insertString (int offset, String str, AttributeSet a) throws BadLocationException {
                 super.insertString(offset, str, a);
                 refreshDocument();
             }
    
             public void remove (int offs, int len) throws BadLocationException {
                 super.remove(offs, len);
                 refreshDocument();
             }
    
             private synchronized void refreshDocument() throws BadLocationException {
                 String text = getText(0, getLength());
                 final List<HiliteWord> list = processWords(text);
    
                 setCharacterAttributes(0, text.length(), _defaultStyle, true);   
                 for(HiliteWord word : list) {
                     int p0 = word._position;
                     setCharacterAttributes(p0, word._word.length(), _cwStyle, true);
                 }
             }       
    
             private static  List<HiliteWord> processWords(String content) {
                 content += " ";
                 List<HiliteWord> hiliteWords = new ArrayList<HiliteWord>();
                 int lastWhitespacePosition = 0;
                 String word = "";
                 char[] data = content.toCharArray();
    
                 for(int index=0; index < data.length; index++) {
                     char ch = data[index];
                     if(!(Character.isLetter(ch) || Character.isDigit(ch) || ch == '_')) {
                         lastWhitespacePosition = index;
                         if(word.length() > 0) {
                             if(isReservedWord(word)) {
                                 hiliteWords.add(new HiliteWord(word,(lastWhitespacePosition - word.length())));
                             }
                             word="";
                         }
                     }
                     else {
                         word += ch;
                     }
                }
                return hiliteWords;
             }
    
             private static final boolean isReservedWord(String word) {
                 return(word.toUpperCase().trim().equals("CROSS") || 
                                word.toUpperCase().trim().equals("CURRENT_DATE") ||
                                word.toUpperCase().trim().equals("CURRENT_TIME") ||
                                word.toUpperCase().trim().equals("CURRENT_TIMESTAMP") ||
                                word.toUpperCase().trim().equals("DISTINCT") ||
                                word.toUpperCase().trim().equals("EXCEPT") ||
                                word.toUpperCase().trim().equals("EXISTS") ||
                                word.toUpperCase().trim().equals("FALSE") ||
                                word.toUpperCase().trim().equals("FETCH") ||
                                word.toUpperCase().trim().equals("FOR") ||
                                word.toUpperCase().trim().equals("FROM") ||
                                word.toUpperCase().trim().equals("FULL") ||
                                word.toUpperCase().trim().equals("GROUP") ||
                                word.toUpperCase().trim().equals("HAVING") ||
                                word.toUpperCase().trim().equals("INNER") ||
                                word.toUpperCase().trim().equals("INTERSECT") ||
                                word.toUpperCase().trim().equals("IS") ||
                                word.toUpperCase().trim().equals("JOIN") ||
                                word.toUpperCase().trim().equals("LIKE") ||
                                word.toUpperCase().trim().equals("LIMIT") ||
                                word.toUpperCase().trim().equals("MINUS") ||
                                word.toUpperCase().trim().equals("NATURAL") ||
                                word.toUpperCase().trim().equals("NOT") ||
                                word.toUpperCase().trim().equals("NULL") ||
                                word.toUpperCase().trim().equals("OFFSET") ||
                                word.toUpperCase().trim().equals("ON") ||
                                word.toUpperCase().trim().equals("ORDER") ||
                                word.toUpperCase().trim().equals("PRIMARY") ||
                                word.toUpperCase().trim().equals("ROWNUM") ||
                                word.toUpperCase().trim().equals("SELECT") ||
                                word.toUpperCase().trim().equals("SYSDATE") ||
                                word.toUpperCase().trim().equals("SYSTIME") ||
                                word.toUpperCase().trim().equals("SYSTIMESTAMP") ||
                                word.toUpperCase().trim().equals("TODAY") ||
                                word.toUpperCase().trim().equals("TRUE") ||
                                word.toUpperCase().trim().equals("UNION") ||
                                word.toUpperCase().trim().equals("UNIQUE") ||
                                word.toUpperCase().trim().equals("WHERE"));
            }
        }
    

    只需像这样将它添加到您的课程中:

        import java.awt.BorderLayout;
        import java.awt.Color;
        import java.awt.Font;
        import javax.swing.JFrame;
        import javax.swing.JScrollPane;
        import javax.swing.JTextPane;
        import javax.swing.text.BadLocationException;
        import javax.swing.text.Style;
        import javax.swing.text.StyleConstants;
        import javax.swing.text.StyleContext;
    
        public class SQLEditor extends JFrame {
            private static final long serialVersionUID = 1L;
    
            public SQLEditor() {
                StyleContext styleContext = new StyleContext();
                Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE);
                Style cwStyle = styleContext.addStyle("ConstantWidth", null);
                StyleConstants.setForeground(cwStyle, Color.BLUE);
                StyleConstants.setBold(cwStyle, true);
    
                final JTextPane pane = new JTextPane(new KeywordStyledDocument(defaultStyle, cwStyle));
                pane.setFont(new Font("Courier New", Font.PLAIN, 12));
    
                JScrollPane scrollPane = new JScrollPane(pane);
                getContentPane().add(scrollPane, BorderLayout.CENTER);
                setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                setSize(375, 400);      
            }
    
            public static void main(String[] args) throws BadLocationException {
                SQLEditor app = new SQLEditor();
                app.setVisible(true);
            }
        }
    

    这是缺少的 HiliteWord 类...

    public class HiliteWord {
    
        int _position;  
        String _word;
    
        public HiliteWord(String word, int position) {
            _position = position;   
            _word = word;
        }
    }
    

    【讨论】:

    • 班级HiliteWord在哪里?
    • @Constantin...请提供 HiliteWord 类的代码
    • 我试过了,但被 4 位评论者中的 3 位拒绝,抱歉
    • 鉴于word.toUpperCase().trim() 不是一个便宜的操作,在最坏的情况下复制和转换字符串内容两次,连续执行70 次并不是一个好主意。考虑到word 不应包含空格,trim() 已过时,equalsIgnoreCase 可以直接执行所需的操作而无需创建新字符串。
    【解决方案3】:

    另一种解决方案是使用DocumentFilter

    这是一个例子:

    创建一个扩展 DocumentFilter 的类:

    private final class CustomDocumentFilter extends DocumentFilter
    {
            private final StyledDocument styledDocument = yourTextPane.getStyledDocument();
    
            private final StyleContext styleContext = StyleContext.getDefaultStyleContext();
            private final AttributeSet greenAttributeSet = styleContext.addAttribute(styleContext.getEmptySet(), StyleConstants.Foreground, Color.GREEN);
            private final AttributeSet blackAttributeSet = styleContext.addAttribute(styleContext.getEmptySet(), StyleConstants.Foreground, Color.BLACK);
    
        // Use a regular expression to find the words you are looking for
        Pattern pattern = buildPattern();
    
        @Override
        public void insertString(FilterBypass fb, int offset, String text, AttributeSet attributeSet) throws BadLocationException {
            super.insertString(fb, offset, text, attributeSet);
    
            handleTextChanged();
        }
    
        @Override
        public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
            super.remove(fb, offset, length);
    
            handleTextChanged();
        }
    
        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attributeSet) throws BadLocationException {
            super.replace(fb, offset, length, text, attributeSet);
    
            handleTextChanged();
        }
    
        /**
         * Runs your updates later, not during the event notification.
         */
        private void handleTextChanged()
        {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    updateTextStyles();
                }
            });
        }
    
        /**
         * Build the regular expression that looks for the whole word of each word that you wish to find.  The "\\b" is the beginning or end of a word boundary.  The "|" is a regex "or" operator.
         * @return
         */
        private Pattern buildPattern()
        {
            StringBuilder sb = new StringBuilder();
            for (String token : ALL_WORDS_THAT_YOU_WANT_TO_FIND) {
                sb.append("\\b"); // Start of word boundary
                sb.append(token);
                sb.append("\\b|"); // End of word boundary and an or for the next word
            }
            if (sb.length() > 0) {
                sb.deleteCharAt(sb.length() - 1); // Remove the trailing "|"
            }
    
            Pattern p = Pattern.compile(sb.toString());
    
            return p;
        }
    
    
        private void updateTextStyles()
        {
            // Clear existing styles
            styledDocument.setCharacterAttributes(0, yourTextPane.getText().length(), blackAttributeSet, true);
    
            // Look for tokens and highlight them
            Matcher matcher = pattern.matcher(yourTextPane.getText());
            while (matcher.find()) {
                // Change the color of recognized tokens
                styledDocument.setCharacterAttributes(matcher.start(), matcher.end() - matcher.start(), greenAttributeSet, false);
            }
        }
    }
    

    您需要做的就是将您创建的DocumentFilter 应用到您的JTextPane,如下所示:

    ((AbstractDocument) yourTextPane.getDocument()).setDocumentFilter(new CustomDocumentFilter());
    

    【讨论】:

    • 这个答案值得更多赞!它对我来说非常有效,而其他答案使用起来更令人困惑。
    • 由于 DocumentFilter 实际上并没有进行任何过滤,因此使用 DocumentListener 可能更有意义。
    【解决方案4】:

    覆盖 paintComponent 对你没有帮助。

    这不是一件容易的事,但也不是不可能的。这样的事情会帮助你:

    DefaultStyledDocument document = new DefaultStyledDocument();
    JTextPane textpane = new JTextPane(document);
    StyleContext context = new StyleContext();
    // build a style
    Style style = context.addStyle("test", null);
    // set some style properties
    StyleConstants.setForeground(style, Color.BLUE);
    // add some data to the document
    document.insertString(0, "", style);
    

    您可能需要对此进行调整,但至少它会告诉您从哪里开始。

    【讨论】:

    • 现在让我在这里问其他小问题,是否可以制作只有 1 行的 JTextPane?就像一个 JTextField
    【解决方案5】:

    没有。您不应该覆盖 paintComponent() 方法。相反,您应该使用StyledDocument。你也应该自己定界。

    这里是演示,输入时将“public”、“protected”和“private”变为红色,就像一个简单的代码编辑器:

    import javax.swing.*;
    import java.awt.*;
    import javax.swing.text.*;
    
    public class Test extends JFrame {
        private int findLastNonWordChar (String text, int index) {
            while (--index >= 0) {
                if (String.valueOf(text.charAt(index)).matches("\\W")) {
                    break;
                }
            }
            return index;
        }
    
        private int findFirstNonWordChar (String text, int index) {
            while (index < text.length()) {
                if (String.valueOf(text.charAt(index)).matches("\\W")) {
                    break;
                }
                index++;
            }
            return index;
        }
    
        public Test () {
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(400, 400);
            setLocationRelativeTo(null);
    
            final StyleContext cont = StyleContext.getDefaultStyleContext();
            final AttributeSet attr = cont.addAttribute(cont.getEmptySet(), StyleConstants.Foreground, Color.RED);
            final AttributeSet attrBlack = cont.addAttribute(cont.getEmptySet(), StyleConstants.Foreground, Color.BLACK);
            DefaultStyledDocument doc = new DefaultStyledDocument() {
                public void insertString (int offset, String str, AttributeSet a) throws BadLocationException {
                    super.insertString(offset, str, a);
    
                    String text = getText(0, getLength());
                    int before = findLastNonWordChar(text, offset);
                    if (before < 0) before = 0;
                    int after = findFirstNonWordChar(text, offset + str.length());
                    int wordL = before;
                    int wordR = before;
    
                    while (wordR <= after) {
                        if (wordR == after || String.valueOf(text.charAt(wordR)).matches("\\W")) {
                            if (text.substring(wordL, wordR).matches("(\\W)*(private|public|protected)"))
                                setCharacterAttributes(wordL, wordR - wordL, attr, false);
                            else
                                setCharacterAttributes(wordL, wordR - wordL, attrBlack, false);
                            wordL = wordR;
                        }
                        wordR++;
                    }
                }
    
                public void remove (int offs, int len) throws BadLocationException {
                    super.remove(offs, len);
    
                    String text = getText(0, getLength());
                    int before = findLastNonWordChar(text, offs);
                    if (before < 0) before = 0;
                    int after = findFirstNonWordChar(text, offs);
    
                    if (text.substring(before, after).matches("(\\W)*(private|public|protected)")) {
                        setCharacterAttributes(before, after - before, attr, false);
                    } else {
                        setCharacterAttributes(before, after - before, attrBlack, false);
                    }
                }
            };
            JTextPane txt = new JTextPane(doc);
            txt.setText("public class Hi {}");
            add(new JScrollPane(txt));
            setVisible(true);
        }
    
        public static void main (String args[]) {
            new Test();
        }
    }
    

    代码不是很漂亮,因为我输入得很快,但它可以工作。我希望它会给你一些提示。

    【讨论】:

    • 有没有其他方法可以在不循环的情况下做同样的事情?
    • @SachinKumar,你是指findLastNonWordCharfindFirstNonWordChar 中的循环吗?是的,您可以不循环找到第一个非单词字符,请参阅答案here,但是没有直接的方法可以找到非单词字符的“最后一个索引”。您可以尝试使用split() 方法。
    • 我正在尝试使用 DocumentListener() 做同样的事情,但我得到了一个异常“尝试在通知中变异”。我如何使用 DocumentListener() 来完成此操作。
    • @SachinKumar,这里的听众不是一回事。 DocumentListener中的所有方法都是在更新发生后触发的。
    • @MrPhi,是的,但是你只能为JTextPane中的所有字符设置相同的颜色(相同的样式,包括字体大小、字体系列、粗体、斜体等)。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    • 2013-11-03
    • 1970-01-01
    相关资源
    最近更新 更多