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

Tagged as

AutoSuggest and AutoComplete control in WPF

, 2 Dec 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Creating an AutoSuggest/AutoComplete control in WPF which follows the MVVM pattern

Introduction

In this article, I am going to share my experience in creating an AutoSuggest/AutoComplete control in WPF which follows the MVVM pattern.

Why Do We Need an AutoSuggest Control?

The dynamic development of the web browsers highlighted the true benefits from the AutoSuggest control’s functionality for the first time- users not only needed to browse, they needed help, they needed instant feedback in a form of a list of suggestions after typing part of a word or phrase. On the other hand, the amount of the presented data made the simple task of choosing from a list without help from an application tedious. And finally, the shortened find-and-choose time in touch screen interfaces where drop-downs are difficult to use made the AutoSuggest controls irreplaceable.

Background

I am currently involved in a WPF desktop application development which manages contracts.

In order to improve the user’s experience and speed up form entry, I decided to use controls in the form of TextBoxes where text is entered by the keyboard. Some of the controls although require picking from a list of choices and the most natural way to implement those without using a ComboBox is to use an AutoSuggest/AutoComplete control.

I did try to find a ready out of the box user control but unfortunately almost all of the controls I found based their implementation on the Microsoft’s ComboBox and just added simple implementation for filtering the items. After not finding what I was looking for, I started talking about the best way of building such an AutoSuggest/AutoComplete control to Orlin Petrov, who is a good friend of mine and is a really outstanding software developer. And this is how we decided to work on it together- him taking the leading architectural role and me doing most of the implementation. This is how KOControls was born.

The code I am providing contains a package of the KOControls library which, as of this writing, consists of generic WPF utilities, implementation of AutoSuggest/AutoComplete control in WPF and a sample project demonstration on how to use it.

Implementation and Usage of the Code

User Requirements

We started with creating a list of high level requirements that our control should meet:

  • The control must be MVVM compliant and the ViewModel should be completely functional without user interface
  • It should be able to hook up to any TextBox and allow AutoSuggest/AutoComplete functionality
  • The control should have an option allowing submission of “free text” and upon confirmation the “free text” to be converted to a valid value
  • It should have a way to inject commands in the suggestion’s window (this is useful when you want to allow the users to edit or create new suggestions from within the suggestion’s control)
  • It should work with copy/paste
  • The control presenting the suggestion should be WPF DataGrid or ListView
  • The control's functionality should be controlled through properties of the ViewModel. (Note that the control would work only if its DataContext is of a particular ViewModel type)
  • It should have an option to insert a filtering algorithm. This will allow third party users to implement an algorithm which fetches the suggested values from a webservice or from a database, to do cleverer filtering overcoming spelling mistakes and auto-correction, etc. A default implementation of the filtering algorithm should be provided.
  • The control should support walking up and down the suggestions with the arrow keys and that should be done without losing TextBox focus
  • It should support the following options for “Cancel” and “Select” behavior:
    • Space may act as selection command or exit command. If it acts as a selection command, it should only select an item if it is the last item in the suggestions list and if the option to allow “free text” values is off.
    • Tab may act as a “Select” or a “Cancel” command. Lost focus is also considered Tab in that sense.
    • Enter may act as a “Select” or a “Cancel” command.
    • The arrow keys may act as a “Select” command.
  • It should support delay in milliseconds before invoking the filtering algorithm. This is useful if the filtering algorithm is heavy and you do not want to invoke it every time the user types a character but want the user to pause and slow down before you invoke the algorithm.
  • The control should have an option to allow or disallow empty values. Also by default, an empty value is the “null” value but users may supply a different empty value.
  • Should have an AutoComplete feature which highlights the completed text after the point where user stopped typing.

There is a screen shot of the AutoSuggestControl in action below:

Example_Screenshot_small.jpg

Project Structure

ProjectStructure_small.png

  1. KO.Controls.Core – Common non UI utilities and core classes and interfaces
  2. KO.Controls.GUI.Core – Common UI utilities like ICommand implementations, converters and utility extension method
  3. KO.Controls.GUI – User controls of theKO.Controls library UI and ViewModels. Currently it has the AutoSuggest user control implementation.
  4. KO.Controls.AutoSuggestTest – Test project demonstrating the usage and helping us with testing the AutoSuggestControls’ functionality (there will be others based on the current control).
  5. KO.Controls.Samples.Core – Common library which contains dummy test data and is to be shared with other Test projects.
  6. KOControls.GUI.Tests - A nice to have project.

Overview of the Class Structure and Implementation

Before I start, I want to give credit to Orlin Petrov’s whose guidance and input on building this nice solution were invaluable.

After discussing which existing .NET controls we should extend/reuse to suite our purpose, we decided on using the WPF TextBox, Selector (ListView, DataGrid, etc.) and popup controls, because they give the most flexibility. The AutoSuggestControl is a user control which you put inside a popup control. The AutoSuggestControl contains a selector control, hooks onto any TextBox, listens to the user’s input, manipulates the selector control’s items and drives the AutoSuggestViewModel.

Below is the view of the AutoSuggestControl’s default templates:

<ResourceDictionary
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
	xmlns:GUI="clr-namespace:KOControls.GUI"
	xmlns:Core="clr-namespace:KOControls.GUI.Core;assembly=KOControls.GUI.Core">

	<!--<span class="code-comment">AutoSuggestControl_Default_SuggestionsTemplate--></span>
	<ControlTemplate x:Key="AutoSuggestControl_Default_SuggestionsTemplate">
		<DataGrid x:Name="PART_Selector" 
		ItemsSource="{Binding Suggestions}" SelectionMode="Single">
		</DataGrid>
	</ControlTemplate>

	<!--<span class="code-comment">AutoSuggestControl_Default_CommandsTemplate--></span>
	<ControlTemplate x:Key="AutoSuggestControl_Default_CommandsTemplate">
		<ItemsControl IsTabStop="False" ItemsSource="{Binding}">
			<ItemsControl.ItemTemplate>
				<DataTemplate>
					<Button Height="25"
						Command="{Binding}"
						Content="{Binding Header}">
					</Button>
				</DataTemplate>
			</ItemsControl.ItemTemplate>
			<ItemsControl.ItemsPanel>
				<ItemsPanelTemplate>
					<StackPanel x:Name="buttonMenuPanel" 
					Orientation="Horizontal" 
					VerticalAlignment="Top">
					</StackPanel>
				</ItemsPanelTemplate>
			</ItemsControl.ItemsPanel>
		</ItemsControl>
	</ControlTemplate>

	<!--<span class="code-comment">AutoSuggestControl_DefaultTemplate--></span>
	<ControlTemplate x:Key="AutoSuggestControl_DefaultTemplate" 
			TargetType="{x:Type GUI:AutoSuggestControl}">
		<Border
			Background="{TemplateBinding Background}"
			BorderBrush="{TemplateBinding BorderBrush}"
			BorderThickness="{TemplateBinding BorderThickness}"
			>
			<Grid>
				<Grid.RowDefinitions>
					<RowDefinition Height="*"></RowDefinition>
					<RowDefinition Height="Auto"></RowDefinition>
				</Grid.RowDefinitions>

				<Control x:Name="_suggestionsContentPresenter" 
					Grid.Row="0"
					Template="{TemplateBinding 
						SuggestionsTemplate}"
					DataContext="{Binding Suggestions}"/>

				<Control x:Name="_commandsContentPresenter" 
					Grid.Row="1"
					Template="{TemplateBinding CommandsTemplate}"
					DataContext="{Binding Commands}"/>
			</Grid>
		</Border>
	</ControlTemplate>
</ResourceDictionary>

The view consists of a grid with two rows. Inside the first row, there is a suggestions’ presenter template which has a default implementation of a DataGrid bound to the suggestions collection in the ViewModel. The second row implies the commands’ template which has a default implementation listing the commands as buttons. The custom commands allow the user to add “new”, “edit” and “details” functionality for any of the suggestions given by the AutoSuggestControl.

The AutoSuggestControl.cs class contains logic which drives and keeps the ViewModel, the Popup, the TextBox and the Selector (DataGrid or ListView) which presents the suggestions in sync. The AutoSuggestControl only works with the AutoSuggestViewModel and you cannot give it any other DataContext.

The AutoSuggestViewModel contains all the business logic for finding suggestions as well as all the options and commands which the user has injected. The actual finding of the suggestions is done by inserting a class which implements the simple ISelector Interface shown below:

public interface ISelector
{
IEnumerable Select(object filter);
}

The filter is the entered text in the TextBox and the IEnumerable are the found suggestions. We provide a default implementation for the ISelector interface which works with a collection of cities and returns the matching cities based on the filter text.

There are two other important dependency properties of the AutoSuggestViewModel: the Suggestion and the SuggestionPreview properties. The Suggestion property represents the currently selected Suggestion. The SuggestionPreview property represents the suggestion the user is about to select (has focus on), but has not yet selected.

How to Use the Control in your Applications

For a comprehensive understanding of the usage of the AutoSuggestControl, I advise you to examine the latest source code at http://code.google.com/p/kocontrols/downloads/list.

I have described three cases below which are very easy to implement using the AutoSuggestControl:

  1. You have a list of cities and you would like to provide the user with a simple and fast way of selecting a city by typing just the first few letters of the city.
  2. You have a list of cities and you would like to provide the user with simple and fast way of selecting a city by typing the first few letters. If the city does not exist in your list, you would like to add it automatically or to pop up an entry window allowing the user to add the city in the list.
  3. You have a list of cities and you would like to provide the user with simple and fast way of selecting a city by typing the first few letters. If the city does not exist in your list, you would like to invoke the “Add New City” window where the new city can be added. If you want to edit any of the existing cities you would like to quickly invoke the “Edit City” window where the cities can be edited.

I’ll cover in detail how to achieve the first case.

The Model

Create a class called “city” which has “Name” and “Country” properties as below:

private string name = "";
private Country country = null;

public string Name { get { return name; } set { if(name != value) { name = value;} } }
public Country Country { get { return country; } set { if(country != value) 
	{ country = value; } } }

Create a class called Country which has a name property as below:

private string name = "";
public string Name { get { return name; } set { if (name != value) { name = value;} } }

Create AutoSuggestConsumerViewModel which has a Collection of cities and a AutoSuggestViewModel property as below:

public class AutoSuggestConsumerViewModelBase : DependencyObject
{
	public AutoSuggestViewModel AutoSuggestVM { get; protected set; }

	public IList<City> AllCities { get; set; }

	public AutoSuggestConsumerViewModelBase()
	{
		AllCities = TestDataService.GetCities();

		IValueConverter valueConverter = 
			new ValueConverter(x => x == null ? "" : ((City)x).Name);
		ISelector selector = new AutoSuggestViewModel.DefaultSelector
					(valueConverter, AllCities);
		AutoSuggestVM = new AutoSuggestViewModel(selector, valueConverter);
	}	
}

Create a view as a UserControl which contains the following XAML snippet:

	...
<Label Grid.Column="0" Content="City:" HorizontalAlignment="Right" />
<TextBox Grid.Column="1" Width="140" Height="22" Padding="0, 3, 0, 0" 
	VerticalAlignment="Top"
			x:Name="_cityTextBox"/>

	<GUI:Popup x:Name="_popup" Placement="Bottom" 
		PlacementTarget="{Binding ElementName=_cityTextBox}">
		<GUI:AutoSuggestControl x:Name="autoSuggest" Focusable="False" 
			OwnerPopup="{Binding ElementName=_popup}"
			TargetTextBox="{Binding ElementName=_cityTextBox, 
			Mode=OneTime}"
			FrameworkElement.DataContext="{Binding AutoSuggestVM}"
			TaboutTrigger="All" 
			ConfirmTrigger="SpaceTabArrows">

			<GUI:AutoSuggestControl.SuggestionsTemplate>
				<ControlTemplate>
					<DataGrid x:Name="PART_Selector"
					CanUserReorderColumns="False" 
					CanUserSortColumns="False" 
					CanUserAddRows="False" 
					CanUserDeleteRows="False" 
					CanUserResizeColumns="True"
					AutoGenerateColumns="False" 
					IsReadOnly="True" 
					HeadersVisibility="None">
					    <DataGrid.Columns>
						<DataGridTextColumn Header="Name" 
						Binding="{Binding Name}" />
						<DataGridTextColumn 
						Header="Country Name" 
						Binding="{Binding Country.Name}" />
					    </DataGrid.Columns>
					</DataGrid>
				</ControlTemplate>
				</GUI:AutoSuggestControl.SuggestionsTemplate>

			</GUI:AutoSuggestControl>
		</GUI:Popup>

The above XAML creates a label, a TextBox and a popup with AutoSuggestControl functionality (i.e., when the user types the first few letters of the city, the popup window opens with the available options).

The most recent source code for the KOControls library and information on implementation of the AutoSuggestControl for the WPF DataGrid could be found at http://code.google.com/p/kocontrols.

The attached files contain a binary with a text application demonstrating how the AutoSuggestControl works as well as the latest source (as of this writing) code of KOControls.

I hope you’ve enjoyed the article. If you are interested in this control, we strongly recommend you to download the source code and play with it. The source code is very simple to figure out.

I will very much appreciate your comments and suggestions and especially ideas and feedback on how to improve the user control.

Ah and if you find a bug, please definitely let me know.

History

  • 30th November, 2011: Initial version

License

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

Share

About the Author

Kostadin Mitev

United States United States
No Biography provided

Comments and Discussions

 
QuestionHow to change filtering column at runtime? Pinmembermaciejmt13-Dec-13 2:10 
AnswerRe: How to change filtering column at runtime? Pinmemberpoporopo20-Jan-14 7:48 
GeneralRe: How to change filtering column at runtime? Pinmembermaciejmt28-Jan-14 10:58 
GeneralRe: How to change filtering column at runtime? Pinmemberpoporopo1-Feb-14 3:51 
QuestionAsync dynamic source? PinmemberDavL8112-Dec-13 6:18 
AnswerRe: Async dynamic source? Pinmemberpoporopo19-Jan-14 5:52 
QuestionSeparators PinmemberCraigGraham1-Jul-13 2:18 
AnswerRe: Separators Pinmemberpoporopo2-Jul-13 21:54 
QuestionDatagrid: how does the value get back to the table? PinmemberCraigGraham19-Jun-13 23:24 
AnswerRe: Datagrid: how does the value get back to the table? Pinmemberpoporopo23-Jun-13 11:40 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 2 Dec 2011
Article Copyright 2011 by Kostadin Mitev
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid