The Missing .NET #3: An AutoComplete TextBox in WPF, Part 2 – Making it reusable

The .NET framework is huge, but not so huge that it does everything for everyone; there are things that they in Redmond miss or don’t do for whatever reason but is still generally applicable to many developers. So, dear reader, I present to you a new series of posts on stuff I find missing in .NET, typically where even the Google fails to find the answer. It could be a useful class, a technique, a good practice or documentation that should be in the framework but isn’t.

In Part 1 last time, I started creating an AutoComplete TextBox in WPF using only the controls given to us by Microsoft and the concepts of WPF. I showed how you can quickly get the meat of the problem solved using a custom ICollectionView that filters based on the text in the TextBox. In this part, I’ll discuss what’s needed to make it reusable.


Alright. So we have a pretty good idea of what’s required, how are we going to package this up to be reused elsewhere? Right now, my implementation is good for the window that I implemented it in. I could make a new control, and add this logic to it, but I already said at the outset that that wasn’t going to happen.

What I decided to use was an attached property. The guidance on using Attached Properties was what finally sold me. See, I also want to use this on ComboBoxes eventually, but ComboBox and TextBox, which both have a Text property, don’t have a common ancestor past Control. One of the scenarios that the docs describe, but don’t provide an example of is wanting to have a property on different, unrelated parts of the control hierarchy. I don’t know why Microsoft can’t provide an example, but I can: the TextSearch class defines two attached properties with this scenario in mind. (Seriously, the docs for .NET 1.1 were awesome. What the hell happened to msdn?)

OK, so I’m going to use an attached property. What that looks like is the following:

public sealed partial class AutoComplete
  public static readonly DependencyProperty SourceProperty;

  static AutoComplete()
     SourceProperty = DependencyProperty.RegisterAttached("Source",
                        typeof (object), typeof (AutoComplete),
                        new FrameworkPropertyMetadata(null,OnSourcePropertyChanged));     

  public static object GetSource(DependencyObject o)
     return o.GetValue(SourceProperty);

  public static void SetSource(DependencyObject o, object value)
     o.SetValue(SourceProperty, value);

This is pretty standard stuff that you can find in the docs. The one piece that’s missing in that snippet is the event handler method OnSourcePropertyChanged that’s declared in the call to DependencyProperty.RegisterAttached. I’ll get to that in Part 3.

What this allows is I can use this on any DependencyObject, like so:

<StackPanel >
    <TextBox Name="textBox1" 
         local:AutoComplete.Source="{Binding Source={StaticResource people}}"
         TextChanged="textBox1_TextChanged" />    

where local is my declared namespace for my assembly (Visual Studio does this for you).

Awesome. I can put this on a ComboBox now, or anything else that allows text input. Now we have an auto-completing TextBox. Your welcome.

Yes, you have a question? There’s some parts missing? Like what? Oh, right the ListBox, it’s not there anymore.

A Look-less Autocomplete TextBox

I’ve already solved the hard problem of getting the List to show up when the user types something; I just need to make that work on any TextBox. The trouble is, I’m manipulating the visual tree, which, to my belated discovery, is really hard to do in code after the TextBox is created. Turns what I want to do is create a ControlTemplate that contains my ListBox and replace the TextBox’s Template with my own.

One of the key concepts in WPF is what the folks at Microsoft call "look-less controls". The canonical example is the button. There are a bajillion — at my last count — samples of making buttons look different than the standard Windows button in WPF. They do this by manipulating either the Content property of the button, or its ControlTemplate. Either way, you still have a button that can be clicked, but you’ve changed the look of the button. All WPF controls act like this, more or less, to some degree: I was asked to make a ListBox turn into a TabControl and that’s non-trivial, nigh impossible, even though they are related in the control hierarchy.

Another example is the ComboBox. I mentioned in the first edition of The Missing .NET that the traditional Win32 ComboBox is made up of three controls: a button, a listbox, and a textbox. Well, so is the WPF ComboBox! Only, the WPF is a little more explicit about it, and more flexible. The place where this is done is the Control.Template dependency property; it’s a dependency property hanging off Control that lets you set the visual tree of your control. The standard controls manipulate this property in their default Style. One tool you want in your WPF developer toolbox is something that will deconstruct the default Styles of the standard controls; I use XamlpadX. Bombing around the styles should give you an understanding of how they work.

Deconstructing the ComboBox in XamlPadX, sure enough, we see the TextBox, the ToggleButton and the ItemsControl, of which ListBox is a descendent.

We’re getting into some seriously advanced, yet fundamental WPF stuff here, which is a little beyond the scope of this blog post, at least if I wanted to finish quickly. If you’re interested in learning about Templates and Styles, Google can be big help; if you’d prefer to learn offline, then any of the WPF books will be quite helpful. Start small, and work your way up. Learning how templates and styles work is definitely worth the effort.

So the only way to stick my ListBox into the visual tree is to replace the ControlTemplate of the TextBox.

This is where the internets came up short for me and I went out on my own. I have no idea if my solution is a good idea. It seems to work, and work well, but I don’t know enough about WPF to tell if this will work in every situation. Every solution creates its own problems and I ran into a few while solving this one.

OK, so what did I do?

You’ll have to wait ’til next time. 🙂