Click here to Skip to main content
Click here to Skip to main content

A Reusable WPF Autocomplete TextBox

By , 5 Jan 2010
 

Introduction

One of the sorely missed features in the WPF control arsenal is an auto-complete text box. In this article, I will create an auto-complete text box, which is similar to what you can find on the web, for example when typing into the Google search box.

I created this text box as a WPF custom control, thereby retaining the ability to style and template the control with maximum flexibility. Additionally I designed this control to be reusable, and to harness the full power of WPF with regards to dependency properties and data binding. The control is very easy to use, simply set the ItemsSource property, and set a Binding property to indicate which data field to use as the completion source. The actual filter is set as a delegate in the code-behind.

There are several road blocks I encountered on my way, and I will go into detail in the next section, but here they are summarized:

  • Exposing the important dependency properties of the list box
  • Focus issues, and how they affect the popup auto-close behavior, and completion list keyboard navigation
  • Evaluating a Binding object on a data item dynamically (from code)
  • Limiting the number of completions shown

In the discussion up ahead, I will not spam the article with code. I will give short examples when necessary, and describe the logic in English. There are two reasons for this, first, there's a lot of code and secondly, I wish to be able to correct and change the code style without having to update the article too much.

Using the Code

I will start by demonstrating a simple use case for the control, to show how easy it is to use it. Here's a minimalistic XAML code that uses an auto-complete text box using the U.S. national weather service observation stations' RSS feed.

Here's how it looks while typing, with keyboard navigation into the completion list:

And here it is after the selection (pressed Enter):

And the code:

<Window x:Class="Test.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:actb="clr-namespace:Aviad.WPF.Controls;assembly=Aviad.WPF.Controls"
    Title="Window1" Height="300" Width="600">
    <Window.Resources>
        <XmlDataProvider x:Key="xml" 
	Source=http://www.nws.noaa.gov/xml/current_obs/index.xml 
	DataChanged="XmlDataProvider_DataChanged"/>
        <DataTemplate x:Key="TheItemTemplate">
            <Border BorderBrush="Salmon" BorderThickness="2" CornerRadius="5">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <TextBlock Text="ID:  "/>
                    <TextBlock Grid.Column="1" Text="{Binding XPath=station_id}"/>
                    <TextBlock Grid.Row="1" Text="Name:  "/>
                    <TextBlock Grid.Column="1" Grid.Row="1" 
			Text="{Binding XPath=station_name}"/>
                    <TextBlock Grid.Row="2" Text="RSS URL:  "/>
                    <TextBlock Grid.Column="1" Grid.Row="2" 
			Text="{Binding XPath=rss_url}"/>
                </Grid>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <TextBlock x:Name="StatusLabel" Text="Loading Data..." Margin="20,20,0,0"/>
        <actb:AutoCompleteTextBox 
            x:Name="actb" 
            Margin="20,40,20,0"
            VerticalAlignment="Top" 
            ItemsSource="{Binding Source={StaticResource xml}, XPath=//station}" 
            ItemTemplate="{StaticResource TheItemTemplate}"
            Binding="{Binding XPath=station_id}" 
            MaxCompletions="10"/>
    </Grid>
</Window>

And here is the code-behind which sets the filter up:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        actb.Filter = Filter;
    }
 
    private bool Filter(object obj, string text)
    {
        XmlElement element = (XmlElement)obj;
        string StationName = 
		element.SelectSingleNode("station_name").InnerText.ToLower();
        if (StationName.Contains(text.ToLower())) return true;
        return false;
    }
 
    private void XmlDataProvider_DataChanged(object sender, EventArgs e)
    {
        StatusLabel.Text = "Xml Data Loaded.";
    }
}

Note how the filter uses the station_name element to filter the auto-completion list, and the XAML specifies that the station_id is extracted from the selected entry. Run the sample and try it out, remember to wait for the XML data to load, it's not small.

In the last section of this article I will show an advanced use case for this text box, using the Google Suggest API.

The Implementation

Conceptually, an auto-complete text box is a text box and a list box in one, but since .NET doesn't allow for multiple inheritance, I had to choose which part is the more prominent one. I chose the TextBox as my base class, and the ListBox control is added as a part in the control template. Additionally, there is the feature which causes the completion list to pop in and out of view when appropriate, and to do that I used a Popup control.

Exposing Dependency Properties

Pushing the list box into the template made several important properties inaccessible, namely:

  • ItemsSource
  • ItemTemplate
  • ItemContainerStyle
  • ItemTemplateSelector

Therefore, to begin with, I exposed these properties in my control. I used the AddOwner method of the appropriate DependencyProperty objects which are part of the ItemsControl class. Here's how it's done for one property, it's similar to the other two.

public static readonly DependencyProperty ItemsSourceProperty =
    ItemsControl.ItemsSourceProperty.AddOwner(
        typeof(AutoCompleteTextBox), 
        new UIPropertyMetadata(null, OnItemsSourceChanged));

Notice that I am assigning a callback method to each of the properties, in the callback method I "forward" the property to the internal list box (with some additional logic).

Next I provide three more properties:

  • Binding - A dependency property which holds the binding used to extract the text to use for auto-completion
  • MaxCompletions - A dependency property for limiting the number of completion results shown
  • Filter - A basic property which holds the callback delegate for filtering the items collection based on the text entered

The auto-complete logic works as follows: For each character typed, the collection view of the list box is refiltered, if at least one match is found, the popup is opened and the list is displayed. A completion entry may be selected by clicking or by using the keyboard. Once a completion entry is chosen, the text obtained using the Binding property from the selected item is put into the text box content, and the popup is closed.

The auto-completion completes successfully in one of the following cases:

  • The user clicks on an item in the list.
  • The user navigates the list using the arrow keys and presses the Return/Enter key or the Tab key on an item.

If the user chose a completion entry by using the keyboard and pressing Tab, the focus also moves to the next control in the tab order of the window.

The auto-completion is aborted in one of the following cases:

  • The user clicks anywhere outside the control area.
  • The user hits Escape.
  • The focus is on the text box itself and the user hits Tab.

Evaluating the Binding

Those of you who are familiar with the previous version of my article, know that I used a dirty hack to transfer the binding from the Binding property of the text box to the data object that is retrieved from the completion list. I recently discovered that I was doing this the wrong way by trying to set the binding on a dummy DependencyObject, and by using a FrameworkElement instead, I was able to use the existing binding and take advantage of the behavior where the default source of the binding is to the DataContext property.

// Retrieve the Binding object from the control.
var originalBinding = BindingOperations.GetBinding(this, BindingProperty);
if (originalBinding == null) return;
 
// Set the dummy's DataContext to our selected object.
dummy.DataContext = obj;
 
// Apply the binding to the dummy FrameworkElement.
BindingOperations.SetBinding(dummy, TextProperty, originalBinding);
 
// Get the binding's resulting value.
Text = dummy.GetValue(TextProperty).ToString();

The 'hack' version is still present in the source download comments, for those who are interested.

Limiting the Completions List

Sometimes the items collection used as the source for completion is very large and consists of thousands of entries. If we do not limit the completions list in some way, we would get a big performance hit on the first few characters we type, because of the time it takes the WPF framework to generate all the visuals that populate the list. We could iterate manually over the collection view and generate a list for display, but this will cause a new list to be generated for every key press. The better way would be to somehow cause the collection view to only expose its first (n) elements.

This is where I had to get creative. To accomplish that, I created a class called LimitedListCollectionView which inherits from ListCollectionView, and bounds size the collection it exposes according to some parameter. Then I overrode the Count property, and the MoveCurrentToNext/Last/Previous/Position methods:

public override int Count { get { return Math.Min(base.Count, Limit); } }
 
public override bool MoveCurrentToLast()
{
    return base.MoveCurrentToPosition(Count - 1);
}
 
public override bool MoveCurrentToNext()
{
    if (base.CurrentPosition == Count - 1)
        return base.MoveCurrentToPosition(base.Count);
    else 
        return base.MoveCurrentToNext();
}
 
public override bool MoveCurrentToPrevious()
{
    if (base.IsCurrentAfterLast)
        return base.MoveCurrentToPosition(Count - 1);
    else
        return base.MoveCurrentToPrevious();
}
 
public override bool MoveCurrentToPosition(int position)
{
    if (position < Count)
        return base.MoveCurrentToPosition(position);
    else
        return base.MoveCurrentToPosition(base.Count);
}
 
#region IEnumerable Members
 
IEnumerator IEnumerable.GetEnumerator()
{
    do
    {
        yield return CurrentItem;
    } while (MoveCurrentToNext());
}
 
#endregion

Notice the intricacies there, I allow the caller to iterate on the first Limit elements, once he tries to move past them, he's transported to the end of the collection.

Other Tricks

There are some other tricks I used in order to make this control truly feel like it's supposed to, and not like an ugly hack. One of those tricks is having the list selection indicator follow the mouse as it moves. Another is trapping the down arrow keypress and transferring focus to the list box to enable keyboard navigation. Additionally I hooked many input events to properly handle the auto-closing of the completion list in case of lost focus, keypresses, etc. I won't copy/paste all the code here. If you are interested, you may look in the source files.

Advanced Use Case - Google Search Box

In this section I will show step by step how to build a google search box, with search suggestions as you type. I will start by defining a "view model". The view model will serve as the backing store for all the data bindings in this example.

The view model defines three properties:

  • QueryText - The backing store for the user's text.
  • QueryCollection - The backing store for the completion list.
  • WaitMessage - Used to tell the user that a query is running in the background.

The QueryCollection property performs a blocking web request to Google's servers, and may take a long time to complete, we bind to it in an asynchronous way, using a PriorityBinding. The WaitMessage property is used as the "fast" property in that same PriorityBinding to notify the user of the pending query. An ItemTemplateSelector is used to select the appropriate template for the case of the wait message.

public class ViewModel : INotifyPropertyChanged
{
    private List<string> _WaitMessage = new List<string>() { "Please Wait..." };
    public IEnumerable WaitMessage { get { return _WaitMessage; } }
 
    private string _QueryText;
    public string QueryText
    {
        get { return _QueryText; }
        set
        {
            if (_QueryText != value)
            {
                _QueryText = value;
                OnPropertyChanged("QueryText");
                _QueryCollection = null;
                OnPropertyChanged("QueryCollection");
            }
        }
    }
 
    public IEnumerable _QueryCollection = null;
    public IEnumerable QueryCollection
    {
        get
        {
            QueryGoogle(QueryText);
            return _QueryCollection;
        }
    }
 
    private void QueryGoogle(string SearchTerm)
    {
        string sanitized = HttpUtility.HtmlEncode(SearchTerm);
        string url = @"http://google.com/complete/search?output=toolbar&q=" + sanitized;
        WebRequest httpWebRequest = HttpWebRequest.Create(url);
        var webResponse = httpWebRequest.GetResponse();
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(webResponse.GetResponseStream());
        var result = xmlDoc.SelectNodes("//CompleteSuggestion");
        _QueryCollection = result;
    }
 
    #region INotifyPropertyChanged Members
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    #endregion
 
    protected void OnPropertyChanged(string prop)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }
}

Next we will create a CollectionViewSource that will hold the results of the query. Since we want the results to be dynamic, we will bind the Source property of the CollectionViewSoruce to the QueryCollection property of the view model. This is where we utilize the PriorityBinding binding class, since we know that the QueryCollection property is a "slow" operation, we put it together with a "fast" property in a PriorityBinding object. The WPF framework will use the "fast" binding until the "slow" one finally provides a value, and then it will notify everyone that the value has changed. Note that the "slow" binding is marked with IsAsync="True"; this causes it to be run in the background, and is required for the PriorityBinding to function correctly.

<CollectionViewSource x:Key="xml">
    <CollectionViewSource.Source>
        <PriorityBinding>
            <Binding Source="{StaticResource vm}"
                     Path="QueryCollection"
                     IsAsync="True"/>
            <Binding Source="{StaticResource vm}" Path="WaitMessage"/>
        </PriorityBinding>
    </CollectionViewSource.Source>
</CollectionViewSource>

Next we declare our auto-complete text box, and properly bind it.

<actb:AutoCompleteTextBox 
    Text=
"{Binding Source={StaticResource vm}, Path=QueryText, UpdateSourceTrigger=PropertyChanged}"
    ItemsSource="{Binding Source={StaticResource xml}}" 
    ItemTemplateSelector="{StaticResource TemplateSelector}"
    Binding="{Binding XPath=suggestion/@data}" 
    MaxCompletions="5"/>

Note that we bind the ItemsSource property to our CollectionViewSource, and our Text property to the view model's QueryText property. It's also important to set the update source trigger properly so that the list updates as we type.

Next in line is the visual templating. Since our completion list can be populated with two types of objects: An XML node list, or a simple list of strings in the case of the wait message, we need to prepare two templates, and to use a template selector. Here are the templates:

<DataTemplate x:Key="TheItemTemplate">
    <Border BorderBrush="Salmon" BorderThickness="2" CornerRadius="5">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock Text="Suggestion:  "/>
            <TextBlock Grid.Column="1" 
                       Text="{Binding XPath=suggestion/@data}"/>
            <TextBlock Grid.Row="1" Text="Results:  "/>
            <TextBlock Grid.Column="1" 
                       Grid.Row="1" 
                       Text="{Binding XPath=num_queries/@int}"/>
        </Grid>
    </Border>
</DataTemplate>
<DataTemplate x:Key="WaitTemplate">
    <TextBlock Text="{Binding}" Background="SlateBlue"/>
</DataTemplate>

And here's the template selector definition:

public class MyDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(
        object item,
        DependencyObject container)
    {
        Window wnd = Application.Current.MainWindow;
        if (item is string)
            return wnd.FindResource("WaitTemplate") as DataTemplate;
        else
            return wnd.FindResource("TheItemTemplate") as DataTemplate;
    }
}

Viola, we're done. No code-behind is needed for the window, since we don't need to assign any special filtering logic to the completion list (we take everything returned by Google).

That's it, enjoy.

History

  • 25th November, 2009: Initial post
  • 27th November, 2009: Google search example added
  • 5th January, 2010: Improved code regarding Binding reapplication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Aviad P.
Software Developer (Senior)
Israel Israel
Member
Software developer since 1984, currently specializing in C# and .NET.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionDelete using X iconmemberMember 78993517 Feb '13 - 7:43 
1st - awesome article!
Question: How would I go about adding an icon "X" on the list-items so I can delete them?
 
Thx!
BugA Reusable WPF Autocomplete TextBox Issuemembermanvindar singh3 Jan '13 - 21:04 
Hi,
When I am accessing this control with remote app It causes crash. But on my local system It is working fine. Can you give me suitable reason or solution for it.
 
Thanks
Manvindar
QuestionCan someone do a code-behind version? [modified]memberJerry Weltman18 May '12 - 9:12 
This code sample is nice, but it does not allow more than one auto complete textbox on a page. I wanted to do a code-behind version, but it was too complicated for me.
 
Instead, I used the "AutoComplete TextBox Control for WPF" by dragonz, which I got by searching for "http://windowsclient.net/blogs/dragonz"
I found it to be much easier to work with.

modified 20 May '12 - 18:44.

GeneralMy Vote Of 5memberShahin Khorshidnia24 Apr '12 - 4:53 
Good article
Thank you
Do not criticise, if you don't have any better idea.

QuestionDesign time NullReferenceExceptionmemberMorten Nilsen6 Mar '12 - 22:47 
Hi, thanks for the contribution to the community!
 
Plugging this into my project, I do however get an ArgumentNullException calling CollectionView..ctor(IEnumerable collection, int32 moveToFirst)
 
From what I can tell, this is due to the else statement being called.
I believe a null check should be added such that you don't have to supply design time data unless you want to:
 
if(itemsSource == null)
{
	listBox.ItemsSource = null;
	Debug.Print("Was null");
}
else if(itemsSource is ListCollectionView)
...
 
It would also be an issue at runtime if one does not want to have empty collections sat around before they are needed.
 
Regards, Morten
AnswerRe: Design time NullReferenceExceptionmemberMorten Nilsen7 Mar '12 - 0:41 
It is mostly working fine now with the above change, but I seem to be having some trouble with filtering the results..
 
What I have right now is a List of ViewModel-objects, that I have used to create a new ListCollectionView. I then applied a filter method to that view.
 
However, when I type text into the search box, the suggestion list does not shrink.
I have tried several things, including recreating the view object, calling view.Refresh() and setting view.Filter anew.
 
I have also sent PropertyNotifiyChange events for the item list property.
 
Debugging the application, the filter predicate does get called for all the items, so I don't quite understand what is going on with that.
Questiongreat sample... help neededmemberchj1242 Feb '12 - 9:24 
i have been trying to convert this to create a VB version... and obviously not everything is easily convertible.
 
i like the layout and potential of your control much better than the others i have come across.
 
have you, or do you know of someone who has, ported this to VB?
chj...
Wink | ;-)

Questiongreat article!memberMember 47736115 Jan '12 - 2:36 
great article
SuggestionFocus issue?memberMickey Mousoff1 Jan '12 - 8:48 
When using up/down keys the focus is being transfered from textbox to the listbox so it is not possible to continue typing (Win7). Any ideas?
GeneralRe: Focus issue? [modified]memberSerdar YILMAZ6 Apr '13 - 23:49 
protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);
            var fs = FocusManager.GetFocusScope(this);
            var o = FocusManager.GetFocusedElement(fs);
            if (e.Key == Key.Escape)
            {
                if (popup != null)
                {
                    if (popup.IsOpen)
                    {
                        e.Handled = true;
                    }
                }
                InternalClosePopup();
                Focus();
            }
 
            if (listBox != null && o == this)
            {
                switch (e.Key)
                {
                    case Key.Down:
                        suppressEvent = true;
 
                        if (listBox.SelectedIndex < listBox.Items.Count)
                            listBox.SelectedIndex = listBox.SelectedIndex + 1;
 
                        suppressEvent = false;
                        break;
 
                    case Key.Up:
                        suppressEvent = true;
 
                        if (listBox.SelectedIndex > -1)
                            listBox.SelectedIndex = listBox.SelectedIndex - 1;
 
                        suppressEvent = false;
                        break;
 
                    case Key.Enter:
                        if (popup.IsOpen)
                        {
                            SetTextValueBySelection(listBox.SelectedItem, false);
                            e.Handled = true;
                        }
                        break;
 
                    case Key.Tab:
                        if (popup.IsOpen)
                            SetTextValueBySelection(listBox.SelectedItem, true);
                        break;
                }
            }
        }
-----------------
 Serdar YILMAZ
Senior Developer


modified 7 Apr '13 - 6:11.

QuestionIs a blocking query required? Async callback won't update itemssourcememberMike Hodnick10 Aug '11 - 4:26 
Awesome job - by far the best WPF implementation of an auto-complete textbox I've seen yet.
 
I'm having trouble getting my ItemsSource to update when binding it to an ObservableCollection and when the collection is changed on an async callback from a JSON service. In my case, it is not preferrable to use a blocking call. When my callback from the service is invoked, my collection is updated, but the auto-complete itemssource does not seem to pick up the new items in my collection. However if I block the QueryText setter thread with a synchronous call then it works.
 
Here's my ViewModel code:
 
string _QueryText;
public string QueryText
{
	get { return _QueryText; }
	set
	{
		_QueryText = value;
		OnPropertyChanged("QueryText");
		this.Suggestions.Clear();
		GetResults();
	}
}
 
public ObservableCollection<Foo> Suggestions { get; set; }
 
BackgroundWorker worker;
void GetResults()
{
	worker.RunWorkerAsync();
}
 
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
	this.Suggestions.Add(new Foo() { Id = "ID: FakeId", Name = "Name: FakeName" });
	// add more suggestions...
	Debug.WriteLine("Done");
}
 
void worker_DoWork(object sender, DoWorkEventArgs e)
{
	Thread.Sleep(1000);
}
 
And here is my XAML:
 
<actb:AutoCompleteTextBox 
	Text="{Binding Source={StaticResource mikevm}, Path=QueryText, UpdateSourceTrigger=PropertyChanged}"
	Margin="20,40,20,0"
	VerticalAlignment="Top" 
	ItemsSource="{Binding Source={StaticResource mikevm}, Path=Suggestions}" 
	Binding="{Binding Name}" 
	MaxCompletions="5">
	<actb:AutoCompleteTextBox.ItemTemplate>
		<DataTemplate>
			<Border BorderBrush="Red" BorderThickness="2">
					<TextBlock Text="{Binding Name}"/>
			</Border>
		</DataTemplate>
	</actb:AutoCompleteTextBox.ItemTemplate>
</actb:AutoCompleteTextBox>
 
My Suggestions collection is constructed in my ViewModel's constructor. If I remove the BackgroundWorker and just add suggestions in the QueryText setter, then the auto-complete data is shown in the control successfully.
Michael Hodnick
www.kindohm.com

GeneralToda Ach Sheli !!!memberantraxant18 Apr '11 - 6:31 
great article !
GeneralIssuememberbrianduper15 Dec '10 - 17:19 
Hi,
 
Very nice article.
 
I tried placing two controls on one form and the weirdest thing happened, when you start typing, the popup, and the text, appears on every autocomplete control, not just the control you are typing in.
 
Is there an easy way to modify this code so each autocompletetextbox is isolated?
 
Many thanks in advance.
GeneralUse with JSON WebservicememberPlew13 Dec '10 - 4:18 
Hi I want to use your AutoCompleter with a Word AddIn an my own JSON Webservice, how can I bind my data to your autocompleter? I'm not really an expert in WPF.
 
ty Stefan
GeneralAdded FeaturesmemberJmiktutt29 Oct '10 - 9:22 
I've worked with this over the past few days, so I could mold it to what I would be needing in the present and future. I have added a couple features. I wanted to include these for anyone who may have use for them as I do.
GeneralSuggest AppendmemberJmiktutt29 Oct '10 - 9:39 
New OnTextChanged Event
protected override void OnTextChanged(TextChangedEventArgs e)
{
	base.OnTextChanged(e);
	if (suppressEvent)
	{ return; }
	<big>string oldtextCache = textCache;</big>
        textCache = Text ?? "";
        Debug.Print("Text: " + textCache);
	if (popup != null && textCache == "")
	{ InternalClosePopup(); }
	else if (listBox != null)
	{
		if (filter != null)
		{ listBox.Items.Filter = FilterFunc; }
 
		if (popup != null)
		{
			if (listBox.Items.Count == 0)
			{ InternalClosePopup(); }
			else
			{
				<big>if (SuggestAppend) { SugApp(e, oldtextCache); }</big>
				InternalOpenPopup();
			}
		}
	}
}
SugApp Method
protected void SugApp(TextChangedEventArgs e, string OldTC)
{
	if (listBox != null && listBox.Items.Count > 0)
	{
		suppressEvent = true;
		if (this.Text == OldTC && e.Changes.First().RemovedLength > 0)
		{ textCache = textCache.Substring(0, this.textCache.Length - 1); }
		object obj = listBox.Items[0];
		var originalBinding = BindingOperations.GetBinding(this, BindingProperty);
		if (originalBinding == null)
		{
			suppressEvent = false;
			return;
		}
		dummy.DataContext = obj;
		BindingOperations.SetBinding(dummy, TextProperty, originalBinding);
		Text = dummy.GetValue(TextProperty).ToString();
		this.Select(textCache.Length, this.Text.Length - this.textCache.Length);
		suppressEvent = false;
	}
}

GeneralSelected ItemmemberJmiktutt29 Oct '10 - 10:15 
I know this one is a little unnecessary, but it should help with the ease of use for the coder. I have also included in this the method for getting the string based off of the Binding Property. With large datasets, the performance on this can cause some issues. A possible solution to this as well as the Binding hack is to have the custom classes that are used in the ItemsSource override the ToString() method. Since I don't know how feasible this is over the full range of features of this control, I chose to include the currently implemented method.
Selected Item Property
public object SelectedItem
{
	get
	{
		if (((IEnumerable<object>)((LimitedCollectionView)listBox.ItemsSource).SourceCollection).Where(a => GetObjectValue(a).ToLower() == this.Text.ToLower()).Count() > 0)
		{ return ((IEnumerable<object>)((LimitedCollectionView)listBox.ItemsSource).SourceCollection).Where(a => GetObjectValue(a).ToLower() == this.Text.ToLower()).First(); }
		else
		{ return null; }
	}
	set
	{
		if (((IEnumerable<object>)((LimitedCollectionView)listBox.ItemsSource).SourceCollection).Contains(value))
		{ this.Text = GetObjectValue(value); }
		else
		{ throw new InvalidOperationException("Item is not contained in source"); }
	}
}
GetObjectValue returns the value of a given object based on the Binding Property.
protected string GetObjectValue(object obj)
{
	var originalBinding = BindingOperations.GetBinding(this, BindingProperty);
	if (originalBinding == null)
	{ return null; }
	dummy.DataContext = obj;
	BindingOperations.SetBinding(dummy, TextProperty, originalBinding);
	return dummy.GetValue(TextProperty).ToString();
}

GeneralVisual Studio Designer Errormembercalvinwillman5 Oct '10 - 17:54 
Great control; though I was getting annoyed by XAML Designer Error in Visual Studio. Identified possible workaround relating to 'null' itemSource being passed in to the CollectionView classes via the OnItemSourceChanged event handler in AutoCompleteTextBox.cs.
 
Added check for DesignMode to return empty IEnumerable, which stopped it from appearing.
 
protected void OnItemsSourceChanged(IEnumerable itemsSource)
{
    if (DesignerProperties.GetIsInDesignMode(this))  //Test for DesignMode
       itemsSource = new List<String>();
 
    if (listBox == null) return;
       Debug.Print("Data: " + itemsSource);
    if (itemsSource is ListCollectionView)
    {
        listBox.ItemsSource = new LimitedListCollectionView((IList)((ListCollectionView)itemsSource).SourceCollection) { Limit = MaxCompletions };
        Debug.Print("Was ListCollectionView");
    }
    else if (itemsSource is CollectionView)
    {
        listBox.ItemsSource = new LimitedListCollectionView(((CollectionView)itemsSource).SourceCollection) { Limit = MaxCompletions };
        Debug.Print("Was CollectionView");
    }
    else if (itemsSource is IList)
    {
        listBox.ItemsSource = new LimitedListCollectionView((IList)itemsSource) { Limit = MaxCompletions };
        Debug.Print("Was IList");
    }
    else
    {
        listBox.ItemsSource = new LimitedCollectionView(itemsSource) { Limit = MaxCompletions };
        Debug.Print("Was IEnumerable");
    }
    if (listBox.Items.Count == 0) InternalClosePopup();
}

GeneralItemsSource is nullmemberpeter.morlion8 Sep '10 - 8:02 
Hi, thank you for your work. I would very much like to use this, but I can't seem to get it to work. I set the ItemsSource of the AutoCompleteTextBox in XAML to a property of my ViewModel. The binding between the View and the ViewModel works, because other controls on the View are bound correctly.
While debugging the ACTB, in the OnItemsSourceChanged(IEnumerable itemsSource) method, the itemsSource is always null however. I thought it might be because I bound to an ObservableCollection property in my ViewModel. But when I use the CollectionViewSource in the XAML (like in your test project), I get the same error.
Any idea what I could be missing?
 
Thank you in advance,
 
Peter
GeneralRe: ItemsSource is nullmemberpeter.morlion13 Sep '10 - 8:00 
No idea what I did wrong (maybe a typo?) but after mucking about a lot, and then trying my original code, it worked.
QuestionThanks, and a questionmemberw1nstrel9 Jun '10 - 7:33 
Hey Aviad, thanks for publishing this, and for including such clear documentation.
 
I have your control data-bound to my own class (similar to how you described it in another post here), but am having trouble with the Filter method. There are no errors when attaching it to the control(s), but it just doesn't seem to filter... even if I have the method return "false" all the time, all the items are displayed when I start typing in the textbox.
 
Any suggestions? (Perhaps something needs to be modified in either the ViewModel or the DataTemplate?)
AnswerRe: Thanks, and a questionmemberAviad P.9 Jun '10 - 10:52 
Did you remember to assign the filter delegate to the Filter property of the text box?
GeneralRe: Thanks, and a questionmemberw1nstrel14 Jun '10 - 11:03 
Oh, I wish it were that easy! I have tried applying the filter in several different ways:
 
1.) Similar to the example in your first response to the "Help for Data Bind" question here, either with a lambda expression or a method in the window object.
 
In this case, ACTB drop-down shows all the items in the collection (with the expected layout from the DataTemplate), and the debugging output shows me that it is running the filter function (and getting the expected answer!) as I mouse over each item.
 
2.) In contrast, I have also tried specifying a CollectionViewSource in the XAML, and connecting the filter event handler there.
 
In this case, again I see all items in the drop-down, but the debug output shows me the filtering decisions, which were again all as expected.
 
Basically, with either technique I can see that the filtering is happening, but it must be on a different view than the control's drop-down is showing.
GeneralRe: Thanks, and a questionmemberAviad P.14 Jun '10 - 11:12 
Can you please provide me with a complete visual studio solution demonstrating the problem (try to reduce it to the minimum needed to reproduce the problem)
 
Use the e-mail link below (on the post footer) to send it to me by e-mail.
 
More likely than not - it's a bug in my code, so I'd like a chance to examine it.
 
Thanks!
GeneralRe: Thanks, and a questionmemberw1nstrel16 Jun '10 - 10:33 
Alright, I'm working on it.
 
The good news (???) is that it seems I can replicate the problem in a small example program.
 
But that does not rule out the possibility that something is off with my binding techniques, too!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 5 Jan 2010
Article Copyright 2009 by Aviad P.
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid