How to change the color of specific words in a JTextPane?

前端 未结 4 1220
醉酒成梦
醉酒成梦 2020-11-28 07:54

How do I change the color of specific words in a JTextPane just while the user is typing? Should I override JTextPane paintComponent m

相关标签:
4条回答
  • 2020-11-28 07:59

    Another solution is to use a DocumentFilter.

    Here is an example:

    Create a class that extends 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);
            }
        }
    }
    

    All you need to do then is apply the DocumentFilter that you created to your JTextPane as follows:

    ((AbstractDocument) yourTextPane.getDocument()).setDocumentFilter(new CustomDocumentFilter());
    
    0 讨论(0)
  • 2020-11-28 08:13

    Overwriting paintComponent will not help you.

    This is not an easy one, but not impossible either. Something like this will help you:

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

    You may need to tweak this, but at least it shows you where to start.

    0 讨论(0)
  • 2020-11-28 08:14

    No. You are not supposed to override the paintComponent() method. Instead, you should use StyledDocument. You should also delimit the words by your self.

    Here is the demo, which turns "public", "protected" and "private" to red when typing, just like a simple code editor:

    enter image description here

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

    The code is not so beautiful since I typed it quickly but it works. And I hope it will give you some hint.

    0 讨论(0)
  • 2020-11-28 08:21

    You can extend DefaultStyledDocument like I did here for an SQL editor I am building with keyword text coloring ...

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

    Simply add it to your class like so:

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

    Here's the missing HiliteWord class ...

    public class HiliteWord {
    
        int _position;  
        String _word;
    
        public HiliteWord(String word, int position) {
            _position = position;   
            _word = word;
        }
    }
    
    0 讨论(0)
提交回复
热议问题