Creating a highlighting text block for Silverlight 3, revisited

Posted 26 August 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’

Templated

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’

RefactorUsings

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!

Jeff Wilcox is a Principal Software Engineer at Microsoft on the Azure team.

Jeff holds a BS in Computer Science from the University of Michigan.

comments powered by Disqus