Binding TextBlock to RichText

Wednesday, October 01, 2008 10:16:41 PM (Central Daylight Time, UTC-05:00)

I was doing some updates to a debugging tool for WPF... I was adding a filter feature that would highlight the characters in yellow as you entered them.

image

I was originally using a TextBlock to bind to by information and that worked fine until I was adding the rich text feature of highlighting the search string.  You can't bind to the Text property of a TextBlock and pass in a Inline object (shown below, which is what supports the rich text within a TextBlock).

image

The only time you could pass Inlines to the TextBlock was in the constructor... that wasn't going to work with a binding.

That forced me down the road of investigating the use of a Label, as I could bind to the Content property and it would render the Inline elements I was creating just fine... this resulted in the rich text highlighting.  However, there was a couple of issues... since this is constantly taking information from the Win32 OutPutDebugString call (see DbgMon) it really needed to perform well when rendering.  I was noticing a little bit of a performance hit... not substantial, but noticeable (maybe that is substantial then).   In addition, wrapping text with in a Label turned out to be not so easy. 

So I went back to the TextBlock.  I tried to bind to the Inlines property.  There was a couple of issues with that... one it wasn't a DependencyProperty and the other issue was that it was read-only.

Solution:

Since I had written a converter to handle highlighting of the filtered text within the string, I was returning Inlines. I modified the convert so that I would return a TextBlock and since I was creating the inlines I was able to pass them into the constructor of the TextBlock.  Instead of binding to a TextBlock in the DataTemplate, I binded to a ContentControl, which would intern using the converter to get the TextBlock that was generated with the filter text highlighted.

The resulting converter looked like the following:

public class StringToHighlightConverter :  IValueConverter
   {
       public object Convert(object values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
       {
           //get the data found in the text
           string dataText = (string)values;
 
           if (!string.IsNullOrEmpty(dataText) && !string.IsNullOrEmpty(TextSearchFilter.SearchClause))
           {
               List<StringOccurance> highlightedWords = new List<StringOccurance>();
               foreach(string token in TextSearchFilter.TokenizedSearchClause)
               {
                   highlightedWords.AddRange(StringEx.FindStringOccurances(dataText, token));
               }
               
               if (highlightedWords.Count == 0)
                   return CreateTextBlock(dataText);
 
               int textStartIdx = 0;
               string plainWord = string.Empty;
               int wordOccuranceIdx = 0; //keep count of the words we have proccessed
               Span phrase = new Span();
 
 
               for (int i = textStartIdx; i < dataText.Length; i++)
               {
                   if (wordOccuranceIdx == highlightedWords.Count)
                   {
                       //We have reached the maximum word occurances, add the plain word span
                       Span wordSpan = CreateWordSpan(dataText.Substring(i), false);
                       phrase.Inlines.Add(wordSpan);
                       break;
                   }
 
                   //get the currrent word that needs to be highlighted
                   StringOccurance wordOccurance = highlightedWords[wordOccuranceIdx];
 
                   if (wordOccurance.StartIndex == i)
                   {
                       //add the plain word span
                       if (!string.IsNullOrEmpty(plainWord))
                       {
                           Span wordSpan = CreateWordSpan(plainWord, false);
                           plainWord = string.Empty;
                           phrase.Inlines.Add(wordSpan);
                       }
 
                       Span highLightedWordSpan = CreateWordSpan(wordOccurance.Value, true);
                       wordOccuranceIdx++;
                       //issue with this
                       i = i + (wordOccurance.Value.Length - 1);
                       phrase.Inlines.Add(highLightedWordSpan);
                       continue;
                   }
 
                   if (i >= dataText.Length)
                   {
                       //add the plain word span
                       //Span wordSpan = CreateWordSpan(dataText.Substring(i), false);
                       Span wordSpan = CreateWordSpan(plainWord, false);
 
                       phrase.Inlines.Add(wordSpan);
                       break;
                   }
 
                   plainWord = plainWord + dataText[i];
               }
 
               return CreateTextBlock(phrase);
 
           }
           return CreateTextBlock(dataText);
       }
 
       private TextBlock CreateTextBlock(Inline text)
       {
           TextBlock block = new TextBlock(text);
           block.TextWrapping = System.Windows.TextWrapping.Wrap;
           return block;
       }
       
       private TextBlock CreateTextBlock(string text)
       {
           Span textSpan = new Span();
           textSpan.Inlines.Add(text);
           return CreateTextBlock(textSpan);
       }
 
       private Span CreateWordSpan(string text, bool highlight)
       {
 
           Span wordSpan = new Span();
           if( highlight == true)
           {
               wordSpan.Background = Brushes.Yellow;
           }
               
           wordSpan.Inlines.Add(text);
           return wordSpan;
 
       }
    
       public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
       {
           throw new NotSupportedException("Not supported");
       }
   }

The xaml in the DataTemplate looked like:

<DataTemplate>
    <ContentControl Content="{Binding OutPutDebugString, 
                    Converter={StaticResource highlightConverter}}" 
                    VerticalAlignment="Top" />
</DataTemplate>