ComponentOne RichTextBox for WPF
Working with WPF RichTextBox / Syntax Coloring
In This Topic
    Syntax Coloring
    In This Topic

    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. In the following section learn how to implement Syntax Coloring in RichTextBox .NET 4.5.2 and .NET 5 versions.

    The code below implements the Syntax coloring feature.

    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.

    The code below implements the Syntax coloring feature.
    C#
    Copy Code
    // Update syntax coloring on a timer
    bool _updating;
    Storyboard _syntax;
    
    // Start the timer whenever the document changes
    private void RichTextboxTextChanged(object sender, C1.WPF.RichTextBox.C1TextChangedEventArgs e)
    {
        if (!_updating)
        {
            // Create storyboard if it's still null
            if (_syntax == null)
            {
                _syntax = new Storyboard();
                _syntax.Completed += SyntaxCompleted;
                _syntax.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
            }
            // Re-start storyboard
            _syntax.Stop();
            _syntax.Seek(TimeSpan.Zero);
            _syntax.Begin();
        }
    }
    
    // Timer elapsed, update syntax coloring
    private void SyntaxCompleted(object sender, EventArgs e)
    {
        _updating = true;
        UpdateSyntaxColoring(richTextbox);
        _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:

    Example Title
    Copy Code
    // Perform syntax coloring
    private void UpdateSyntaxColoring(C1RichTextBox richTextbox)
    {
        // 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 = richTextbox.Text;
        var range = richTextbox.GetTextRange(0, input.Length);
        range.Foreground = richTextbox.Foreground;
        // Highlight the matches
        foreach (Match m in Regex.Matches(input, pattern))
        {
            // Select whole tag, make it dark blue
            range = richTextbox.GetTextRange(m.Index, m.Length);
            range.Foreground = brDarkBlue;
            // Select tag name, make it dark red
            var tagName = m.Groups["tagName"];
            range = richTextbox.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 = richTextbox.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.