Link to home
Start Free TrialLog in
Avatar of Ron Miller
Ron Miller

asked on

Formatting rich text

Hi there, I am using C# wpf and trying to operate on a rich text finding and replacing words and formatting them in bold italics etc by using code behind. What is the simplest way to do it as I have read many complicated options and can't decide on one. Thanks.
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

I made an extension method to make it -really- easy to do search and replaces on WPF RichTextBox elements with an optional callback for formatting. Example showing a button click that searches for all instances of "foo" and replaces them all with "bar" and then calls the HighlightReplacedResults method on each of the replaced items to format them:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            myRichTextBox.SearchAndReplace("foo", "bar", HighlightReplacedResults);
        }

        private void HighlightReplacedResults(TextRange replacedTextRange)
        {
            replacedTextRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Blue);
            replacedTextRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
        }

Open in new window


The extension method is below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Documents;

namespace Extensions
{
    public static class RichTextBoxExtensions
    {
        public static void SearchAndReplace(this RichTextBox rtfBox, string searchFor = "foo", string replaceWith = "bar", Action<TextRange> formattingCallback = null)
        {
            // Find all Run elements that contain the search text
            List<Run> searchResults = new List<Run>();
            foreach (var block in rtfBox.Document.Blocks)
            {
                if (block is Paragraph)
                {
                    var paragraph = (Paragraph)block;
                    foreach (var inline in paragraph.Inlines)
                    {
                        if (inline is Run)
                        {
                            Run run = (Run)inline;
                            if (run.Text.Contains(searchFor))
                            {
                                searchResults.Add(run);
                            }
                        }
                    }
                }
            }

            while (searchResults.Count > 0)
            {
                // Work our way from the end, processing the Runs 1-by-1
                Run run = searchResults.Last();
                searchResults.Remove(run);

                // Now find all the text ranges in our run, in case there's more than one.
                List<int> positionsInRun = new List<int>();
                int searchOffset = 0;
                int searchPosition = -1;
                do
                {
                    searchPosition = run.Text.IndexOf(searchFor, searchOffset);
                    searchOffset = searchPosition + 1;
                    if (searchPosition >= 0)
                    {
                        positionsInRun.Add(searchPosition);
                    }

                } while ((searchOffset < run.Text.Length) && (searchPosition != -1));

                // Now replace all of them
                run.Text = run.Text.Replace(searchFor, replaceWith);

                // If we have any formatting callbacks, then we'll need to first get the TextRange objects that represent the replaced words
                if (formattingCallback != null)
                {
                    int replaceLengthDifference = replaceWith.Length - searchFor.Length;
                    int offsetShift = 0;
                    TextPointer runStart = run.ContentStart;
                    List<TextRange> textRanges = new List<TextRange>();
                    foreach (var i in positionsInRun)
                    {
                        int startingOffset = i + offsetShift;
                        int endingOffset = i + offsetShift + replaceWith.Length;

                        TextRange replacedTextRange = new TextRange(runStart.GetPositionAtOffset(startingOffset), runStart.GetPositionAtOffset(endingOffset));
                        textRanges.Add(replacedTextRange);
                        offsetShift += replaceLengthDifference;
                    }

                    // Finally, once we have all the ranges, apply any optional formatting callbacks
                    foreach (var textRange in textRanges)
                    {
                        formattingCallback(textRange);
                    }
                }
            }
        }
    }
}

Open in new window


If you haven't used extension methods before, just save the above as a separate class file (I used Extensions.cs), and in your code-behind file where you want to use the SearchAndReplace() call, just add "using Extensions;" to the top of that file so that it can see that class.


NOTE: I'm certain there are some things that could be tweaked/optimized a bit in here (like the part that finds the replaced text ranges), but once I got it working, I just saved it as-is in a library and reused it from that point on and it worked pretty well.

I also have not tested it with any complex documents.
This question needs an answer!
Become an EE member today
7 DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform.
View membership options
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.