Java 如何更改JTextPane中特定单词的颜色?

Java 如何更改JTextPane中特定单词的颜色?,java,swing,jtextpane,Java,Swing,Jtextpane,如何在用户键入时更改JTextPane中特定单词的颜色? 我应该重写JTextPanepaintComponent方法吗?重写paintComponent对您没有帮助 这并非易事,但也并非不可能。类似这样的东西将帮助您: DefaultStyledDocument document = new DefaultStyledDocument(); JTextPane textpane = new JTextPane(document); StyleContext context = new Styl

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

重写
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);

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

否。您不应该重写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();
    }
}

import javax.swing.*;
导入java.awt.*;
导入javax.swing.text.*;
公共类测试扩展了JFrame{
私有int findLastNonWordChar(字符串文本,int索引){
而(--index>=0){
如果(String.valueOf(text.charAt(index)).matches(“\\W”)){
打破
}
}
收益指数;
}
私有int findFirstNonWordChar(字符串文本,int索引){
while(索引而(wordR另一种解决方案是使用
文档过滤器

以下是一个例子:

创建扩展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());

您可以扩展DefaultStyledDocument,就像我在这里使用关键字文本着色构建的SQL编辑器一样

    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"));
        }
    }
这是缺少的HiliteWord类

public class HiliteWord {

    int _position;  
    String _word;

    public HiliteWord(String word, int position) {
        _position = position;   
        _word = word;
    }
}
@康斯坦丁

亲爱的康斯坦丁:, 我在我的小项目中使用了你的优秀解决方案,经过几次调整后,你的解决方案对我很有效

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

我在自己的JFrame中使用您的类关键字StyledDocument:

StyleContext styleContext = new StyleContext();
Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE);
这行我改了: MutableAttributeSet cwStyle=Functions.style(true、false、Color.RED)

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

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的实例。因此,我自然也必须更改您的关键字StyledDocumentClass的构造函数:

public KeywordStyledDocument(Style defaultStyle, MutableAttributeSet cwStyle) {
    _defaultStyle =  defaultStyle;
    _cwStyle = cwStyle;
}
在此之后,请更改并在isReservedWord函数中添加我自己的“单词”,并将我的字符和*添加到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 == '*')) {
我成了我的口哨:


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

这个关于的答案可能也会对您有所帮助。现在让我在这里问另外一个小问题,是否可以制作一个只有一行的JTextPane?就像JTextField一样,有没有其他方法可以不循环地做同样的事情?@SachinKumar,您是指findLastNonWordChar中的循环
findFirstNonWordChar
?是的,您可以不循环地查找第一个非单词字符,请参见答案,但没有直接的方法来查找非单词字符的“最后一个索引”。您可以尝试使用
split()
方法。我正在使用DocumentListener()尝试同样的方法,但我遇到了一个异常“尝试在通知中进行变异”。如何使用DocumentListener()完成此操作。@SachinKumar,listener在这里不是一回事。
DocumentListener
中的所有方法都是在发生更新后触发的。@MrPhi,是的,但您只能设置相同的颜色(相同的样式,包括字体大小、字体系列、粗体、斜体等)对于JTextPane中的所有字符。这个答案值得更多的喜欢!它对我来说非常有效,而其他答案使用起来更容易混淆。因为DocumentFilter实际上不做任何过滤,所以使用a可能更有意义。类
HiliteWord
?@Constantin在哪里?@Constantin…请提供HiliteWo的代码rd classI尝试过,但被4位审阅者中的3位拒绝,很抱歉,鉴于
word.toUpperCase().trim()
不是一个便宜的操作,在最坏的情况下复制和转换字符串内容两次,最多连续执行70次不是一个好主意。考虑到
word
不应该包含空格,
trim()
已过时,
equalsIgnoreCase
可以直接执行所需的操作,而无需创建新字符串。
public KeywordStyledDocument(Style defaultStyle, MutableAttributeSet cwStyle) {
    _defaultStyle =  defaultStyle;
    _cwStyle = cwStyle;
}
 ...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 == '*')) {