Creating a highlighting text block for Silverlight 3, revisited
August 26, 2009
Now that Silverlight 3 has shipped, I’d like to take a moment to revisit the highlighting AutoCompleteBox control that I blogged about back in November of ‘08, and again earlier this year, thanks to tooling improvements: Expression Blend 3 is out, and the Visual Studio 2008 tools have changed as well. There are new project and item templates this time around.
This short post re-creates the HighlightingTextBlock control, using the Templated Silverlight Control item template that ships in the Silverlight Tools. When I last blogged about the highlighting text block control, I had to describe in detail how to go about creating a library, creating the default control styles file (Generic.xaml), setting properties, and putting it all together.
Now it is a lot easier! Using the advanced copy-and-paste coding technique, you can create and build this control in about 2 minutes.
Create a new Silverlight Class Library Project
- Open Visual Studio 2008 SP1
- File | New Project, Visual C# | Silverlight | Silverlight Class Library project type
Remove Class1.cs
The default class file, Class1.cs, can be removed. Right-click on it in the Solution Explorer and select the ‘Delete’ menu item.
Use the ‘Silverlight Templated Control’ template
The new template is great since it creates a simple class for the control, sets up the default style key, and then creates/modifies the Generic.xaml theme file for the library, setting all the right properties along the way.
- Click on the Project menu (or right-click on the project in the Solution Explorer)
- Select ‘Add New Item’
- Use the ‘Silverlight Templated Control’ template
- Change the name from TemplatedControl1.cs to HighlightingTextBlock.cs
- Click ‘Add’
Insert the control code
Borrowed from my previous post on the topic, just paste this class’ code into the namespace, replacing what is already there:
/// <summary> /// A specialized highlighting text block control. /// </summary> public partial class HighlightingTextBlock : Control { /// <summary> /// The name of the TextBlock part. /// </summary> private string TextBlockName = "Text"; /// <summary> /// Gets or sets the text block reference. /// </summary> private TextBlock TextBlock { get; set; } /// <summary> /// Gets or sets the inlines list. /// </summary> private List<Inline> Inlines { get; set; } #region public string Text /// <summary> /// Gets or sets the contents of the TextBox. /// </summary> public string Text { get { return GetValue(TextProperty) as string; } set { SetValue(TextProperty, value); } } /// <summary> /// Identifies the Text dependency property. /// </summary> public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof(string), typeof(HighlightingTextBlock), new PropertyMetadata(OnTextPropertyChanged)); /// <summary> /// TextProperty property changed handler. /// </summary> /// <param name="d">AutoCompleteBox that changed its Text.</param> /// <param name="e">Event arguments.</param> private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { HighlightingTextBlock source = d as HighlightingTextBlock; if (source.TextBlock != null) { while (source.TextBlock.Inlines.Count > 0) { source.TextBlock.Inlines.RemoveAt(0); } string value = e.NewValue as string; source.Inlines = new List<Inline>(); if (value != null) { for (int i = 0; i < value.Length; i++) { Inline run = new Run { Text = value[i].ToString() }; source.TextBlock.Inlines.Add(run); source.Inlines.Add(run); } source.ApplyHighlighting(); } } } #endregion public string Text #region public string HighlightText /// <summary> /// Gets or sets the highlighted text. /// </summary> public string HighlightText { get { return GetValue(HighlightTextProperty) as string; } set { SetValue(HighlightTextProperty, value); } } /// <summary> /// Identifies the HighlightText dependency property. /// </summary> public static readonly DependencyProperty HighlightTextProperty = DependencyProperty.Register( "HighlightText", typeof(string), typeof(HighlightingTextBlock), new PropertyMetadata(OnHighlightTextPropertyChanged)); /// <summary> /// HighlightText property changed handler. /// </summary> /// <param name="d">AutoCompleteBox that changed its HighlightText.</param> /// <param name="e">Event arguments.</param> private static void OnHighlightTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { HighlightingTextBlock source = d as HighlightingTextBlock; source.ApplyHighlighting(); } #endregion public string HighlightText #region public Brush HighlightBrush /// <summary> /// Gets or sets the highlight brush. /// </summary> public Brush HighlightBrush { get { return GetValue(HighlightBrushProperty) as Brush; } set { SetValue(HighlightBrushProperty, value); } } /// <summary> /// Identifies the HighlightBrush dependency property. /// </summary> public static readonly DependencyProperty HighlightBrushProperty = DependencyProperty.Register( "HighlightBrush", typeof(Brush), typeof(HighlightingTextBlock), new PropertyMetadata(null, OnHighlightBrushPropertyChanged)); /// <summary> /// HighlightBrushProperty property changed handler. /// </summary> /// <param name="d">HighlightingTextBlock that changed its HighlightBrush.</param> /// <param name="e">Event arguments.</param> private static void OnHighlightBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { HighlightingTextBlock source = d as HighlightingTextBlock; source.ApplyHighlighting(); } #endregion public Brush HighlightBrush #region public FontWeight HighlightFontWeight /// <summary> /// Gets or sets the font weight used on highlighted text. /// </summary> public FontWeight HighlightFontWeight { get { return (FontWeight)GetValue(HighlightFontWeightProperty); } set { SetValue(HighlightFontWeightProperty, value); } } /// <summary> /// Identifies the HighlightFontWeight dependency property. /// </summary> public static readonly DependencyProperty HighlightFontWeightProperty = DependencyProperty.Register( "HighlightFontWeight", typeof(FontWeight), typeof(HighlightingTextBlock), new PropertyMetadata(FontWeights.Normal, OnHighlightFontWeightPropertyChanged)); /// <summary> /// HighlightFontWeightProperty property changed handler. /// </summary> /// <param name="d">HighlightingTextBlock that changed its HighlightFontWeight.</param> /// <param name="e">Event arguments.</param> private static void OnHighlightFontWeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { HighlightingTextBlock source = d as HighlightingTextBlock; FontWeight value = (FontWeight)e.NewValue; } #endregion public FontWeight HighlightFontWeight /// <summary> /// Initializes a new HighlightingTextBlock class. /// </summary> public HighlightingTextBlock() { DefaultStyleKey = typeof(HighlightingTextBlock); Loaded += OnLoaded; } /// <summary> /// Loaded method handler. /// </summary> /// <param name="sender">The loaded event.</param> /// <param name="e">The event data.</param> private void OnLoaded(object sender, RoutedEventArgs e) { OnApplyTemplate(); } /// <summary> /// Override the apply template handler. /// </summary> public override void OnApplyTemplate() { base.OnApplyTemplate(); // Grab the template part TextBlock = GetTemplateChild(TextBlockName) as TextBlock; // Re-apply the text value string text = Text; Text = null; Text = text; } /// <summary> /// Apply the visual highlighting. /// </summary> private void ApplyHighlighting() { if (Inlines == null) { return; } string text = Text ?? string.Empty; string highlight = HighlightText ?? string.Empty; StringComparison compare = StringComparison.OrdinalIgnoreCase; int cur = 0; while (cur < text.Length) { int i = highlight.Length == 0 ? -1 : text.IndexOf(highlight, cur, compare); i = i < 0 ? text.Length : i; // Clear while (cur < i && cur < text.Length) { Inlines[cur].Foreground = Foreground; Inlines[cur].FontWeight = FontWeight; cur++; } // Highlight int start = cur; while (cur < start + highlight.Length && cur < text.Length) { Inlines[cur].Foreground = HighlightBrush; Inlines[cur].FontWeight = HighlightFontWeight; cur++; } } } }
Then, refactor the Using statements to make the code a little crisper:
- Right-click on one of the ‘using’ statements at the top of the file
- Select ‘Organize Usings’, then ‘Remove and Sort’
Define the default control style
Now, Generic.xaml is already created in the Themes folder – so go ahead and open it, then use this for the control template. Our default style is simple: sets the default highlight brush color, plus a single template part – a text block named ‘Text’.
<Style TargetType="local:HighlightingTextBlock"> <Setter Property="HighlightBrush" Value="Blue" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:HighlightingTextBlock"> <TextBlock x:Name="Text" /> </ControlTemplate> </Setter.Value> </Setter> </Style>
Build the project, and you’re good to go and use that control now. Hope this helps!