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!