Thursday, December 14, 2006 12:15 PM
Tamir Khason
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.
תגים:WPF, tutorial, source