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, I roughed in my AutoComplete Textbox with standard WPF controls. In Part 2, I decided to deploy this as an attached property but ran into some trouble with inserting my WPF controls into an control’s visual tree. In this part below, I show how I solved the visual tree problem using ControlTemplates.
Charles Petzold has an example of creating a ControlTemplate in code in his Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation (Pro – Developer). It involves creating a nested set of factories: truly painful. So I quickly abandoned it as a tenable method for replacing the ControlTemplate. That meant a XAML solution for writing out the ControlTemplate.
But a XAML solution creates its own problems, especially since I’ll have to access the objects created by the template in code (the CollectionViewSource for one, and another control that has yet to make its appearance in this saga). Again, my ignorance with these advanced WPF concepts forced a solution that may not be the best one: I decided to use a ResourceDictionary.
The ResourceDictionary is damn near ubiquitous in WPF. Both FrameworkElement and FrameworkContentElement have a Resources property that allow you to store arbitrary .NET objects, so every control has a Resources property. This is often where styles or data sources are stored, but it can be any .NET object. You can also create your own ResourceDictionary as a standalone class, typically as App-level resources or as a Theme. The cool thing about ResourceDictionary, if you associate it with a class is that you can combine XAML and code like you do in a Window class. Since I’ve already got my AutoComplete class started, what I did was add a ResourceDictionary to my project and used the Class attribute (in the XAML namespace) to associate it with my AutoComplete class.
Now I have to write my own ControlTemplate for TextBox that will allow me to stick my ListBox to it. I could start from scratch, but then I’d have to recreate all the behaviour of the TextBox, including borders, colours, mouse over behaviour, etc, and I want to get this done. I have XamlPadX, so why not pull out the default TextBox style and manipulate it? It’s got the standard look and everything. So that’s what I do: stick that in my ResourceDictionary and change the template to add the ListBox, like this:
<ControlTemplate TargetType="TextBoxBase"> <StackPanel> <mwt:ListBoxChrome Name="Bd"> <ScrollViewer Name="PART_ContentHost" /> </mwt:ListBoxChrome> <Popup x:Name="autoCompletePopup" Placement="Bottom" PlacementTarget="{Binding ElementName=Bd}" StaysOpen="False" AllowsTransparency="True"> <ListBox x:Name="AutoCompleteListBox" ItemsSource="{Binding Source={StaticResource viewSource}}" /> </Popup> </StackPanel> <ControlTemplate.Triggers ...> </ControlTemplate>
This is the relevant piece where I set the Template property on the Control. You can view the entire ResourceDictionary here (Or Download the source). It differs from the standard one in a few places: 1) The StackPanel that contains the TextBox and 2) the Popup containing the ListBox with the name AutoCompleteListBox. Why use the Popup? I’ll leave that as an exercise for the reader. 🙂 Note the ListBoxChrome element with the ScrollViewer inside it. The ListBoxChrome element resides in the Presenation.Aero assembly. Not listed is the Style.Resources property where I add the CollectionViewSource with the Filter event handled.
The methods for handling the CollectionViewSource.Filter and the TextBox.TextChanged events remain the same, I just have to move them to the AutoComplete class and hook them up to the TextBox when it’s passed to me in the OnSourcePropertyChanged event handler when my attached property changes, about which I mentioned earlier in Part 2. I did this with a private, read-only DependencyProperty that creates an AutoComplete instance to associate with the TextBox as the code below shows:
private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { AutoComplete ac = new AutoComplete(); ac.TextBox = (Control)d; ac.ViewSource.Source = e.NewValue; d.SetValue(AutoCompleteInstancePropertyKey, ac); } internal Control TextBox { set { control = value; Style s = (Style)this["autoCompleteTextBoxStyle"]; viewSource = (CollectionViewSource)control.GetViewSource(s); viewSource.Filter += CollectionViewSource_Filter; value.SetValue(Control.StyleProperty, this["autoCompleteTextBoxStyle"]); value.ApplyTemplate(); autoCompletePopup = (Popup) value.Template.FindName("autoCompletePopup", value); value.AddHandler(System.Windows.Controls.TextBox.TextChangedEvent, new TextChangedEventHandler(textBox1_TextChanged)); value.LostFocus += textBox1_LostFocus; value.PreviewKeyUp += textBox1_PreviewKeyUp; } }
In the AutoComplete.TextBox setter is where I set the TextBox’s Style to my style and grab my CollectionViewSource and a reference to the Popup.
That’s essentially it. We’ve created an autocomplete TextBox in WPF! But what’s the point when you don’t take advantage of the new tech? Next time I’ll explain a couple of improvements we can make to the AutoComplete TextBox and show that adding support from ComboBox is really straightforward.
Very well done, I can confirm writing a decent autocomplete box is a challenge. I will likely use this as a more portable replacement for the approach I took. It is very similar to what I did.
One thing I have an issue with is that I am unable to control the style of the textbox in my XAML. It appears to be overridden by style set in AutoComplete.xaml. How do you think I should go about allowing for custom styles.