Working with RichTextBox for WPF and Silverlight > Syntax Coloring |
The Understanding C1TextPointer section describes how you can use the Selection property to obtain a C1TextRange object that corresponds to the current selection, and how to use that object to inspect and apply custom formatting to parts of the document.
In some cases, however, you may want to inspect and apply formatting to ranges without selecting them. To do that using the Selection property, you would have to save the current selection, apply all the formatting, and then restore the original selection. Also, changing the selection may cause the document to scroll in order to keep the selection in view.
To handle these situations, the C1RichTextBox exposes a GetTextRange method. The GetTextRange method returns a C1TextRange object that may be used without affecting the current selection.
For example, you could use the GetTextRange method to add HTML syntax coloring to a C1RichTextBox. The first step is to detect any changes to the document. The changes will trigger the method that performs the actual syntax coloring:
Visual Basic |
Copy Code
|
---|---|
' Update syntax coloring on a timer Private _updating As Boolean Private _syntax As Storyboard ' Start the timer whenever the document changes Private Sub tb_TextChanged(sender As Object, e As C1TextChangedEventArgs) If Not _updating Then ' Create storyboard if it's still null If _syntax Is Nothing Then _syntax = New Storyboard() AddHandler _syntax.Completed, AddressOf _syntax_Completed _syntax.Duration = New Duration(TimeSpan.FromMilliseconds(1000)) End If ' Re-start storyboard _syntax.[Stop]() _syntax.Seek(TimeSpan.Zero) _syntax.Begin() End If End Sub ' Timer elapsed, update syntax coloring Private Sub _syntax_Completed(sender As Object, e As EventArgs) _updating = True UpdateSyntaxColoring(_rtb) _updating = False End Sub |
C# |
Copy Code
|
---|---|
// Update syntax coloring on a timer bool _updating; Storyboard _syntax; // Start the timer whenever the document changes void tb_TextChanged(object sender, C1TextChangedEventArgs e) { if (!_updating) { // Create storyboard if it's still null if (_syntax == null) { _syntax = new Storyboard(); _syntax.Completed += _syntax_Completed; _syntax.Duration = new Duration(TimeSpan.FromMilliseconds(1000)); } // Re-start storyboard _syntax.Stop(); _syntax.Seek(TimeSpan.Zero); _syntax.Begin(); } } // Timer elapsed, update syntax coloring void _syntax_Completed(object sender, EventArgs e) { _updating = true; UpdateSyntaxColoring(_rtb); _updating = false; } |
The code creates a timer that starts ticking whenever the user changes the document in any way. If the user changes the document while the timer is active, then the timer is reset. This prevents the code from updating the syntax coloring too often, while the user is typing quickly.
When the timer elapses, the code sets a flag to prevent the changes made while updating the syntax coloring from triggering the timer, then calls the UpdateSyntaxColoring method:
Visual Basic |
Copy Code
|
---|---|
' Perform syntax coloring Private Sub UpdateSyntaxColoring(rtb As C1RichTextBox) ' Initialize regular expression used to parse HTML Dim pattern As String = "</?(?<tagName>[a-zA-Z0-9_:\-]+)" & "(\s+(?<attName>[a-zA-Z0-9_:\-]+)(?<attValue>(=""[^""]+"")?))*\s*/?>" ' Initialize brushes used to color the document Dim brDarkBlue As Brush = New SolidColorBrush(Color.FromArgb(255, 0, 0, 180)) Dim brDarkRed As Brush = New SolidColorBrush(Color.FromArgb(255, 180, 0, 0)) Dim brLightRed As Brush = New SolidColorBrush(Colors.Red) ' Remove old coloring Dim input = rtb.Text Dim range = rtb.GetTextRange(0, input.Length) range.Foreground = rtb.Foreground ' Highlight the matches For Each m As Match In Regex.Matches(input, pattern) ' Select whole tag, make it dark blue range = rtb.GetTextRange(m.Index, m.Length) range.Foreground = brDarkBlue ' Select tag name, make it dark red Dim tagName = m.Groups("tagName") range = rtb.GetTextRange(tagName.Index, tagName.Length) range.Foreground = brDarkRed ' Select attribute names, make them light red Dim attGroup = m.Groups("attName") If attGroup IsNot Nothing Then Dim atts = attGroup.Captures For i As Integer = 0 To atts.Count - 1 Dim att = atts(i) range = rtb.GetTextRange(att.Index, att.Length) range.Foreground = brLightRed Next End If Next End Sub |
C# |
Copy Code
|
---|---|
// Perform syntax coloring void UpdateSyntaxColoring(C1RichTextBox rtb) { // Initialize regular expression used to parse HTML string pattern = @"</?(?<tagName>[a-zA-Z0-9_:\-]+)" + @"(\s+(?<attName>[a-zA-Z0-9_:\-]+)(?<attValue>(=""[^""]+"")?))*\s*/?>"; // Initialize brushes used to color the document Brush brDarkBlue = new SolidColorBrush(Color.FromArgb(255, 0, 0, 180)); Brush brDarkRed = new SolidColorBrush(Color.FromArgb(255, 180, 0, 0)); Brush brLightRed = new SolidColorBrush(Colors.Red); // Remove old coloring var input = rtb.Text; var range = rtb.GetTextRange(0, input.Length); range.Foreground = rtb.Foreground; // Highlight the matches foreach (Match m in Regex.Matches(input, pattern)) { // Select whole tag, make it dark blue range = rtb.GetTextRange(m.Index, m.Length); range.Foreground = brDarkBlue; // Select tag name, make it dark red var tagName = m.Groups["tagName"]; range = rtb.GetTextRange(tagName.Index, tagName.Length); range.Foreground = brDarkRed; // Select attribute names, make them light red var attGroup = m.Groups["attName"]; if (attGroup != null) { var atts = attGroup.Captures; for (int i = 0; i < atts.Count; i++) { var att = atts[i]; range = rtb.GetTextRange(att.Index, att.Length); range.Foreground = brLightRed; } } } } |
The code starts by defining a regular expression pattern to parse the HTML. This is not the most efficient way to parse HTML, and the expression is not terribly easy to read or maintain. We don't recommend using regular expressions for parsing HTML except in sample code, where it may help keep the code compact and easy to understand.
The next step is to remove any old coloring left over. This is done by creating a range that spans the whole document and setting its Foreground property to match the Foreground of the C1RichTextBox control.
Next, the regular expression is used to parse the document. The code scans each match, creates a C1TextRange object, and sets the C1TextRange.Foreground property to the desired value. We use dark blue for the HTML tag, dark red for the tag name, and light red for the attribute names.
That's all the code that is required. The image below shows an HTML document viewed in the syntax-coloring C1RichTextBox we just created:
Test the application by typing or pasting some HTML text into the control. Notice that shortly after you stop typing, the new text is colored automatically.
A real application could optimize the syntax coloring process by detecting the type of text change and updating the coloring of small parts of the document. Also, it would detect additional elements such as style sheets and comments, and it probably would use a specialized parser instead of regular expressions.
The essential mechanism would be the same, however: detect ranges within the document, get C1TextRange objects, and apply the formatting.