"There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies and the other is to make it so complicated that there are no obvious deficiencies". C.A.R. Hoare
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.
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).
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;
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)
//Span wordSpan = CreateWordSpan(dataText.Substring(i), false);
plainWord = plainWord + dataText[i];
return CreateTextBlock(phrase);
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>
Posted in Code | Development | WPF |Comments [563]
Sysknowlogy