RichTextBox syntax highlighting

14 בדצמבר 2006

תגיות: , ,
19 תגובות

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/richtextbox-syntax-highlighting/]


Today we'll learn how to create RichTextBox with syntax highlight ing. Syntax highlighting is a feature of some text editors that displays text—especially source code—in different colors and fonts according to the category of terms. This feature eases writing in a structured language such as a programming language or a markup language as both structures and syntax errors are visually distinct.

The first thing we need is RichTextBox. So, do it. I did some trick on it in order to prevent from <P> (Paragraph) inserting on each enter (with is default behavior of RichTextBox). I want only one line down when I pressed my Enter key.

<RichTextBox ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" Name="TextInput" AcceptsReturn="True" TextChanged="TextChangedEventHandler">
      <RichTextBox.Resources>
        <Style TargetType="{x:Type Paragraph}">
          <Setter Property="Margin" Value="0"/>
        </Style>
      </RichTextBox.Resources>
    </RichTextBox>

Ok, not I want only plaintext in my TextBox TextChanged event 'cos I'm going to color it myself.

private void TextChangedEventHandler(object sender, TextChangedEventArgs e)
        {
            if (TextInput.Document == null)
                return;
 
            TextRange documentRange = new TextRange(TextInput.Document.ContentStart, TextInput.Document.ContentEnd);
            documentRange.ClearAllProperties();

 

Now let's create navigator to go though the text and hightlight it

TextPointer navigator = TextInput.Document.ContentStart;
            while (navigator.CompareTo(TextInput.Document.ContentEnd) < 0)
            {
                TextPointerContext context = navigator.GetPointerContext(LogicalDirection.Backward);
                if (context == TextPointerContext.ElementStart && navigator.Parent is Run)
                {
                    CheckWordsInRun((Run)navigator.Parent);
 
                }
                navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);
            }

 

So the only thing we have to do is to check each word within my navigator, compare it to Highlighting dictionary and color it. Huh, where is my dictionary? Here it comes. I'll do it for ActionScript for my client.

class JSSyntaxProvider
   {
       static List<string> tags = new List<string>();
       static List<char> specials = new List<char>();
       #region ctor
       static JSSyntaxProvider()
       {
           string[] strs = {
               "Anchor",
               "Applet",
               "Area",
               "Array",
               "Boolean",.....
tags = new List<string>(strs);
 
We also want to know all possible delimiters so adding this stuff.
 
            char[] chrs = {
                '.',
                ')',
                '(',
                '[',
                ']',
                '>',
                '<',
                ':',
                ';',
                '\n',
                '\t'
            };
            specials = new List<char>(chrs);
 

Now I should check statically if the string I passed is legal and constants in my dictionary

public static bool IsKnownTag(string tag)
        {
            return tags.Exists(delegate(string s) { return s.ToLower().Equals(tag.ToLower()); });
        }

Also, I'll do kind of Intellisense later, so I want to check the beginnings of my tags as well

public static List<string> GetJSProvider(string tag)
        {
            return tags.FindAll(delegate(string s) { return s.ToLower().StartsWith(tag.ToLower()); });
        }

Wow. Great. Now I should separate words, that equals to my tags. For this propose we'll create new internal structure named Tag. This will help us to save words and its' positions.

new struct Tag
        {
            public TextPointer StartPosition;
            public TextPointer EndPosition;
            public string Word;
 
        }

How, let's go through our text and save all tags we have to save.

int sIndex = 0;
            int eIndex = 0;
            for (int i = 0; i < text.Length; i++)
            {
                if (Char.IsWhiteSpace(text[i]) | JSSyntaxProvider.GetSpecials.Contains(text[i]))
                {
                    if (i > 0 && !(Char.IsWhiteSpace(text[i - 1]) | JSSyntaxProvider.GetSpecials.Contains(text[i - 1])))
                    {
                        eIndex = i - 1;
                        string word = text.Substring(sIndex, eIndex - sIndex + 1);
 
                        if (JSSyntaxProvider.IsKnownTag(word))
                        {
                            Tag t = new Tag();
                            t.StartPosition = run.ContentStart.GetPositionAtOffset(sIndex, LogicalDirection.Forward);
                            t.EndPosition = run.ContentStart.GetPositionAtOffset(eIndex + 1, LogicalDirection.Backward);
                            t.Word = word;
                            m_tags.Add(t);
                        }
                    }
                    sIndex = i + 1;
                }
            }

How this works. But wait. If the word is last word in my text I'll never hightlight it, due I'm looking for separators. Let's add some fix for this case

string lastWord = text.Substring(sIndex, text.Length - sIndex);
            if (JSSyntaxProvider.IsKnownTag(lastWord))
            {
                Tag t = new Tag();
                t.StartPosition = run.ContentStart.GetPositionAtOffset(sIndex, LogicalDirection.Forward);
                t.EndPosition = run.ContentStart.GetPositionAtOffset(eIndex + 1, LogicalDirection.Backward);
                t.Word = lastWord;
                m_tags.Add(t);
            }

How I have all my words and its' positions in list. Let's color it! Dont forget to unsubscribe! text styling fires TextChanged event.

TextInput.TextChanged -= this.TextChangedEventHandler;
 
            for(int i=0;i<m_tags.Count;i++)
            {
                TextRange range = new TextRange(m_tags[i].StartPosition, m_tags[i].EndPosition);
                range.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Blue));
                range.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
            }
            m_tags.Clear();
 
            TextInput.TextChanged += this.TextChangedEventHandler;

That's all. We have nice RichTextBox, that understands and color any syntax we want. Maybe later we'll add Intellisense for it.

Source code of this article

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים

19 תגובות

  1. DrorEngel14 בדצמבר 2006 ב 15:17

    please provide the direct link to the source
    thanks

    להגיב
  2. dudub15 בדצמבר 2006 ב 22:59

    again you are doing it, taking some thing hard and making it very easy with a well understood implementation.

    Great job :-)

    להגיב
  3. softer10 בינואר 2007 ב 16:03

    It's very very low on bigger (regular)!!!
    WPF sometimes sucks!

    להגיב
  4. bs24 באפריל 2007 ב 4:06

    great sample–just what i was looking for, thanks

    להגיב
  5. Rajesh Kumar12 באוקטובר 2007 ב 12:11

    You don't need to define something called "Tag". You can use TextRange instead, which serves for the same.
    See this sample: http://blogs.msdn.com/prajakta/archive/2006/11/01/navigate-words-in-richtextbox.aspx

    להגיב
  6. Pavol9 בינואר 2008 ב 14:04

    Good work, but it's so horribly slow…

    להגיב
  7. Pavol10 בינואר 2008 ב 10:45

    this is the row: that slow things down:

    documentRange.ClearAllProperties();

    להגיב
  8. Tom10 בינואר 2008 ב 11:56

    Try instead of documentRange.ClearAllProperties();
    just set the background and foreground colors of the whole FlowDocument to white and black :)

    להגיב
  9. Pavol10 בינואר 2008 ב 16:29

    Doesn't work. Text still remains colored. The only possible way I found so far is take only actual word where the cursor stands a make Syntax highlighting on it. The rest remains as it is.
    It's good for one phrase keywords like public static etc. but if you want colorize string: "this is colorized string"
    or comments: // this is definitely green text
    then I'm lost.

    I have a syntax coloring in old .NET RichTextBox, where you can search your words three times there and back again, colorize it and it's fast enough, here if you color more than 3 words it's totally unusable…
    If anyone found a better solution, please share with us :)

    להגיב
  10. ¨DmitryBOYKO4 באפריל 2008 ב 20:28

    Your code works very slowly.

    להגיב
  11. XeoniX12 באפריל 2008 ב 18:49

    most part of this sample was taken from Windows Vista SDK 9.0 in
    WPF-Samples section. There in nothing new… this way of implementation (of Microsoft's developers) is very slow, dependly
    from word cont in RichTextBox :( ((

    i'll try to do something with it, but when anybody find a solution,
    please let me know to "xeonix@i.ua".

    …thanks

    להגיב
  12. Jim3 במאי 2008 ב 0:24

    I won't thank you, instead I'll just bitch and whine about how this doesn't suit me. Why are you not pandering to my needs? I think I've waited long enough for the solution I need.

    להגיב
  13. Nick7 במאי 2008 ב 23:16

    I agree with all who say this is slow – great tutorial, but the end result runs at a snail's pace. Any clues?

    להגיב
  14. My Bad24 בדצמבר 2008 ב 17:10

    Very slow + A complete rip off from another source. Greate job anyway.

    להגיב