StackOverflowException when using TextPointer.GetLineStartPosition in RichTextBox_TextChanged event

I have a RichTextBox (RTB) and next to it a DataGrid (DG). A user can paste a list of IDs into the RTB to perform some bulk actions on them. In order that users can be aware if an ID they passed into the RTB isn’t in the DG, I want to change the colour of the text.

This is what I have at the moment:

private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    //TODO: Enforce 13 character line limit and replace any delimiters with new line

    //Get all text
    RichTextBox rtb = (RichTextBox)sender;
    TextRange textRange = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd);

    //Split into lines
    string[] rtbLines = textRange.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

    foreach (string line in rtbLines)
    {
        //remove comms, but could some others
        line.Replace(",", "");

        //Get the index of the start of an ID
        int startPos = textRange.Text.IndexOf(line);

        //this should be unncessary?
        if (startPos > -1)
        {
            //Get a pointer to text we found
            TextPointer tp = textRange.Start.GetPositionAtOffset(startPos, LogicalDirection.Backward);

            //this should be unnecessary?
            if (tp != null)
            {
                //Get the line start position
                TextPointer tpLine = tp.GetLineStartPosition(0); //This throws StackOverflowException sometimes?

                //Get start of next line
                TextPointer testLineEnd = tp.GetLineStartPosition(1);

                //In case we're at the end, default to end of the document
                TextPointer tpLineEnd = (testLineEnd ?? tp.DocumentEnd).GetInsertionPosition(LogicalDirection.Backward);

                //Get text range between line start/end
                TextRange lineText = new TextRange(tpLine, tpLineEnd);

                //Cast the data and check if our text is in it so we can check it
                if (UnassignedMPANsListView.ItemsSource != null && UnassignedMPANsListView.ItemsSource.Cast<CrmUnassignedMPAN>().ToList().Any(x => x.MPAN == line))
                {
                    lineText.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
                }
                else
                {
                    lineText.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Black);
                }
            }
        }
    }
}

The issue is that often the line TextPointer tpLine = tp.GetLineStartPosition(0); causes a StackOverflowException. It seems to be only when the user is typing an ID; pasting seems to be OK.

My thought is that this is caused because the RichTextBox_TextChanged event is being called repeatedly, whereas pasting is a single hit, but I don’t understand why that would cause this exception rather than just become very slow.

Also open to any other suggestions for how to implement this feature. My key requirements are that a user can paste a list, type them 1 by 1, and the rows can be programmatically coloured indepedently.

Answer

You have exception because you have recursive call at the place lineText.ApplyPropertyValue(TextElement.ForegroundProperty this call does modify your text. To avoid it you can simply add the flag:

bool changeIsRunning = false;
private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    if(changeIsRunning)
    {
        return;
    }
    changeIsRunning = true;
    try
    {
      //Put your logic here.
    }
    finally
    {
        changeIsRunning = false;
    }
}

Be aware, that there are not only visible text, but also invisible element tags in the text(e.g. for the color).