【问题标题】:efficient way to color text in JTextPane在 JTextPane 中为文本着色的有效方法
【发布时间】:2015-09-06 01:46:01
【问题描述】:

我在为 JTextPane 中的某些关键字着色时遇到问题。换句话说,我想制作一个迷你 IDE 之类的东西,所以我会编写一些代码,并且我想为“public”“private”等关键字赋予颜色(比如蓝色)。问题是它非常慢!因为每次我点击“空格”或“退格”键时,该函数都会扫描整个文本以为关键字提供颜色,所以当我在文本窗格中编写大量代码时,它会变得非常慢。 这是我匹配关键字的功能:

public void matchWord() throws BadLocationException {
        String tokens[] = ArabicParser.tokenNames;
        int index = 0;
        String textStr[] = textPane.getText().split("\\r?\\n");
        for(int i=0 ; i<textStr.length ; i++) {
            String t = textStr[i];
            StringTokenizer ts2 = new StringTokenizer(t, " ");
            while(ts2.hasMoreTokens()) {
                String token = ts2.nextToken();

                // The iterations are reduced by removing 16 symbols from the search space
                for(int j = 3 ; j<tokens.length-5 ; j++) {
                    if(!(token.equals("؛")) && (tokens[j].equals("'"+token+"'"))) {
                        changeColor(textPane,token,Color.BLUE,index,token.length());
                        break;
                    } else {
                        changeColor(textPane,token,Color.BLACK,index,token.length());
                    }
                }
                index += token.length() + 1;
            }
            //index -= 1;
        }
    }

这是我为匹配的单词着色的功能:

private void changeColor(JTextPane tp, String msg, Color c, int beginIndex, int length) throws BadLocationException {
        SimpleAttributeSet sas = new SimpleAttributeSet(); 
        StyleConstants.setForeground(sas, c);
        StyledDocument doc = (StyledDocument)tp.getDocument();
        doc.setCharacterAttributes(beginIndex, length, sas, false);
        sas = new SimpleAttributeSet(); 
        StyleConstants.setForeground(sas, Color.BLACK);
        tp.setCharacterAttributes(sas, false);
    }

提前致谢 =)

【问题讨论】:

  • 无需检查整个文本,只需更改行,

标签: java swing colors jtextpane


【解决方案1】:

您可以使用 DocumentListener 仅分析插入到 TextPane 中的文本。这样,您无需多次分析整个文本,只需检查添加的内容。

为此,您需要获取javax.swing.text.Utilities 类的getWordStartgetWordEnd 方法。这样就可以获取到插入位置的周边上下文。

编辑:删除可以改变关键字的状态。移除时,需要获取移除起始位置与getWordStart之间的文字,以及移除结束位置与getWordEnd之间的文字。例如,如果从“intercontinental surface”中删除“continental sur”,则会得到可能是关键字的“interface”。

例如,您可以使用此类:

import javax.swing.text.Utilities;
public class Highlighter implements DocumentListener {

    public void insertUpdate(final DocumentEvent e) {
        highlight(e.getDocument(),e.getOffset(),e.getLength());
    }

    public void removeUpdate(DocumentEvent e) {
        highlight(e.getDocument(), e.getOffset(), 0);
    }

    public void changedUpdate(DocumentEvent e) {}

    private void highlight(final Document doc, final int offset, final int length) {
        //Edit the color only when the EDT is ready
        SwingUtilities.invokeLater(new Runnable() 
            public void run() {
                //The impacted text is the edition + the surrounding part words.
                int start = Utilities.getWordStart(myJTextPane,offset);
                int end = Utilities.getWordEnd(myJTextPane,offset+length);
                String impactedText = doc.getText(start,end-start);
                applyHighlighting(doc, impactedText, offset);
            }
        });
    }

    private void applyHighlighting(Document doc, String text, int offset) {
        //we review each word and color them if needed.
        StringTokenizer tokenizer = new StringTokenizer(text, " \t\n\r\f,.:;?![]'()");
        int start = 0;
        while(tokenizer.hasMoreTokens()) {
            String word = tokenizer.nextToken();
            start = text.indexOf(word,start+1);
            if(isKeyword(word)) {
                //you can use the method you proposed for instance as a start.
                changeColor(myJTextPane, word, Color.BLUE, start, word.length());
            } else if(offset==0 || !tokenizer.hasMoreTokens()) {
                //The first and last word's state can have changed. 
                //We need to put them back in BLACK if needed.
                changeColor(myJTextPane, word, Color.BLACK, start, word.length());
            }
        }
    }
}

【讨论】:

  • (1+) 用于 DocumentListener,但不要忘记删除文本会影响突出显示。此外,您可以粘贴多行文本。因此,您需要考虑这些情况以获得通用解决方案。
  • 你完全正确。我忘了删除部分。我的编辑应该解决这个问题。关于多行,其实还是只需要前一个wordStart和下一个wordEnd。受影响的文本是 wordStart+insertedText+wordEnd。
  • 是的,wordStart/wordEnd 概念适用于处理任意数量的粘贴文本。当我编写突出显示代码时,我也在处理字符串文字,因此我需要更多文本来确保您没有突出显示在文字字符串中找到的标记。当您需要处理 cmets 时,标记化也会变得更加复杂。请注意 getText(...) 方法的参数是 Document.getText(start, length) 而不是 (start, end),因此您将在当前示例中标记额外的文本。
  • getText 确实如此。对不起,我编辑了我的答案。关于字符串文字和 cmets,实际上您的方法是不够的,因为字符串或注释可以在几段之前开始。但是 IMO,要处理这个问题,您需要在突出显示关键字后执行此操作。然后您解析整个文本并查找打开/关闭引号或 cmets。然后你突出显示整个部分,这已经足够高效了。
【解决方案2】:

问题是它非常慢!因为每次我点击“空格”或“退格”键时,该功能都会扫描整个文本

您可以通过仅处理更改的行来提高效率。

当文档发生更改时,DocumentListener 可用于通知您。然后,您可以仅解析受更改影响的行。请记住,文本窗格中可能会粘贴多行文本,因此您需要处理这种情况。

以下是 DocumentListener 的简单结构的一些(未经测试的)代码,您可以使用它来仅处理更改的行:

public class KeywordDocumentListener implements DocumentListener
{
    public void insertUpdate(final DocumentEvent e)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                processChangedLines(e.getDocument(), e.getOffset(), e.getLength());
            }
        });
    }

    public void removeUpdate(DocumentEvent e)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                processChangedLines(e.getDocument(), e.getOffset(), 0);
            }
        });
    }

    public void changedUpdate(DocumentEvent e) {}

    private void processChangedLines(Document doc, int offset, int length)
    {
        //  The lines affected by the latest document update

        Element rootElement = doc.getDefaultRootElement();
        int startLine = rootElement.getElementIndex(offset);
        int endLine = rootElement.getElementIndex(offset + length);

        //  Do the highlighting one line at a time

        for (int i = startLine; i <= endLine; i++)
        {
            int lineStart = rootElement.getElement( i ).getStartOffset();
            int lineEnd = rootElement.getElement( i ).getEndOffset() - 1;
            String lineText = doc.getText(lineStart, lineEnd - lineStart);
            applyHighlighting(doc, lineText, lineStart);
        }
    }

    private void applyHighlighting(Document doc, String text, int lineStart)
    {
        // Now you can search a line of text for your keywords
        // As you find a keyword to highlight you add the lineStart to the search
        // location so the highlight is the proper offset in the Document
    }
}

invokeLater() 是必需的,因为您无法在 DocumentListener 中更新 Document,因此这会将代码放在 EDT 的末尾,以便在侦听器完成执行后执行。

对于简单的解析,我认为使用 StringTokeninzer 没有问题。它会比使用正则表达式更有效。

为关键字赋予颜色,

实际上,您所做的不仅仅是为关键字着色,您还在为每个普通单词着色,这不是很有效。我建议您将整行文本设置为黑色前景色。然后,在您的解析中,您仅突出显示您找到的带有蓝色的标记。这将显着减少对 Document 进行的属性更改次数。

不要为每个标记创建一个新的 AttributeSet。创建一次 AttributeSet,然后为每个令牌重复使用它。

【讨论】:

    【解决方案3】:

    考虑替换 StringTokenizer,因为它的现代用途不受欢迎 https://stackoverflow.com/a/6983908/1493294

    考虑将String tokens[] 重构为HashSet&lt;String&gt; tokens。哈希查找将比循环更快,尤其是当tokens[] 变大时。

    如果您想使用两种以上的颜色,请尝试HashMap&lt;String, Color&gt; tokens

    另外,有两个非常不同的东西tokentokens 在这里跑来跑去是令人困惑的。考虑将tokens[] 重命名为coloredNames[],这样它就明显不同于textPane 令牌中的token

    考虑使用分析器来查看大部分时间都花在了哪里。您可能会发现在 changeColor() 中完成的重复性工作值得缓存。

    如果是这样,请编写一个名为 ColorChanger 的类。 ColorChanger 将有一个构造函数和一个方法changeColor()。构造函数将采用(并因此缓存)在循环时不会更改的参数。 ColorChanger.changeColor() 将采用在您循环时会更改的参数。

    【讨论】:

    • 我看不出有什么理由要替换 StringTokenizer。选择的答案参数不适用于此处,评分最高的答案表明使用似乎并不气馁。我也认为您的回答没有考虑问题的核心,即处理的数据量太重要且不必要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-20
    • 2014-06-05
    • 2011-04-15
    相关资源
    最近更新 更多