Click here to Skip to main content
12,749,754 members (37,886 online)
Click here to Skip to main content
Add your own
alternative version


34 bookmarked
Posted 21 Dec 2008

WPF AutoComplete Folder TextBox

, 4 Sep 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
This article demos how to create a textbox which can auto-suggest items at runtime based on input, in this case, disk drive folders.


This article demos how to create a textbox which can auto-suggest items at runtime based on input, in this case, disk drive folders.


There are a number of auto-complete textbox implementations around; however, some don't support data binding, and others don't support runtime items polling. After some Googling, I decided to write my own instead of continuing to look for one.

My design process

My first design is based on the ComboBox. I copy the default template and remove the drop down button and develop from that. It doesn't work because the combobox has its own autocomplete mechanism which will change the selection of the textbox when items are changed; it's not designed for items that change at real-time.

The second design is based on a TextBox. I create the following style:

<Style x:Key="autoCompleteTextBox" TargetType="{x:Type TextBox}">
  <Setter Property="Template">
      <ControlTemplate TargetType="{x:Type TextBoxBase}">
        <Grid x:Name="root">
          <ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
          <Popup x:Name="PART_Popup" 
                      {x:Static SystemParameters.ComboBoxPopupAnimationKey}}">
                <ListBox x:Name="PART_ItemList" 
                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                      KeyboardNavigation.DirectionalNavigation="Contained" />

and then create a custom control and hook the style to it:

<TextBox x:Class="QuickZip.Controls.SelectFolderTextBox" 
  Style="{DynamicResource autoCompleteTextBox}" > 
  ... </TextBox>

PART_ContentHost is actually a TextBoxView; it is required for the TextBox template (with that name), or the control won't function. The other two parts (PART_Popup and PART_ItemList) are defined so I can use them in the custom control:

public partial class SelectFolderTextBox : TextBox
   Popup Popup { get { return this.Template.FindName("PART_Popup", this) as Popup; } }
   ListBox ItemList { get { return 
        this.Template.FindName("PART_ItemList", this) as ListBox; } }
   ScrollViewer Host { get { return 
        this.Template.FindName("PART_ContentHost", this) as ScrollViewer; } }
   UIElement TextBoxView { get { 
        foreach (object o in LogicalTreeHelper.GetChildren(Host)) 
             return o as UIElement; return null; } }


If text is changed, the suggestion item list is updated as well:

protected override void OnTextChanged(TextChangedEventArgs e)
  if (_loaded)
      if (lastPath != Path.GetDirectoryName(this.Text))      
        lastPath = Path.GetDirectoryName(this.Text);
        string[] paths = Lookup(this.Text); //Get subdirectory of current

        foreach (string path in paths)
          if (!(String.Equals(path, this.Text, 
      Popup.IsOpen = true;
      //I added a Filter so Directory polling is only called once 
      //per directory, thus improve speed
      ItemList.Items.Filter = p =>
        string path = p as string;
        return path.StartsWith(this.Text, StringComparison.CurrentCultureIgnoreCase)&&
         !(String.Equals(path, this.Text, StringComparison.CurrentCultureIgnoreCase));

A number of handlers is then defined:

public override void OnApplyTemplate()
  _loaded = true;
  this.KeyDown += new KeyEventHandler(AutoCompleteTextBox_KeyDown);
  this.PreviewKeyDown += new KeyEventHandler(AutoCompleteTextBox_PreviewKeyDown);
  ItemList.PreviewMouseDown += 
      new MouseButtonEventHandler(ItemList_PreviewMouseDown);
  ItemList.KeyDown += new KeyEventHandler(ItemList_KeyDown);


If the user pressed the down button, the textbox will move the focus to the listbox so the user can choose an item from it; this is placed in PreviewKeyDown instead of KeyDown because TextBox's mechanism will consume the event before it reaches KeyDown if the button is the down button.

void AutoCompleteTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
  if (e.Key == Key.Down && ItemList.Items.Count > 0
   && !(e.OriginalSource is ListBoxItem))
    ItemList.SelectedIndex = 0;
    ListBoxItem lbi = ItemList.
     ItemContainerGenerator.ContainerFromIndex(ItemList.SelectedIndex) as ListBoxItem;
    e.Handled = true;


If the user presses the <Enter> button, the textbox will close the popup and update the binding.

void AutoCompleteTextBox_KeyDown(object sender, KeyEventArgs e)
  if (e.Key == Key.Enter)
    Popup.IsOpen = false;

ItemList_PreviewMouseDown and ItemList_PreviewMouseDown

If the user presses the <Enter> button (or select by mouse), the text textbox will be updated with ListBox.SelectedValue, and then we update the binding.

void ItemList_KeyDown(object sender, KeyEventArgs e)
  if (e.OriginalSource is ListBoxItem)
    ListBoxItem tb = e.OriginalSource as ListBoxItem;
    Text = (tb.Content as string);
    if (e.Key == Key.Enter)
      Popup.IsOpen = false;

void ItemList_PreviewMouseDown(object sender, MouseButtonEventArgs e)
  if (e.LeftButton == MouseButtonState.Pressed)
  {          {
    TextBlock tb = e.OriginalSource as TextBlock;
    if (tb != null)
      Text = tb.Text;
      Popup.IsOpen = false;
      e.Handled = true;

updateSource is required because I bound the text's UpdateSourceTrigger as Explicit; if updateSource is not called, it won't update the text:

void updateSource()
  if (this.GetBindingExpression(TextBox.TextProperty) != null)

The component is working now, but if you want to add validation as well, read below:

To support validation, a Validation Rule is written

If the path is not found or an exception is raised when looking up, it will return ValidationResult as false, and the error will be accessed by using the attached properties Validation.Errors and Validation.HasError.

public class DirectoryExistsRule : ValidationRule
  public static DirectoryExistsRule Instance = new DirectoryExistsRule();

  public override ValidationResult Validate(object value, 
         System.Globalization.CultureInfo cultureInfo)
      if (!(value is string))
        return new ValidationResult(false, "Invalid Path");

      if (!Directory.Exists((string)value))
        return new ValidationResult(false, "Path Not Found");
    catch (Exception ex)
      return new ValidationResult(false, "Invalid Path");
    return new ValidationResult(true, null);

and we change the binding to use the created Validation Rule; note that UpdateSourceTrigger is Explicit.

<local:SelectFolderTextBox  x:Name="stb" 
           DockPanel.Dock="Bottom" Margin="4,0,0,0">
    <Binding Path="Text" UpdateSourceTrigger="Explicit" >
        <t:DirectoryExistsRule />

Now the textbox shows a red border if the directory does not exist. As a red border isn't clear enough, we can change the behavior to disable the default red border:

<Style x:Key="autoCompleteTextBox" TargetType="{x:Type TextBox}">
  <Setter Property="Validation.ErrorTemplate">
      <ControlTemplate >
        <AdornedElementPlaceholder /> <!-- The TextBox Element -->

then change the control template, which will show the dockWarning when Validation.HasError:

<ControlTemplate TargetType="{x:Type TextBoxBase}">
  <Border Name="Border" CornerRadius="2"  
      Background="{StaticResource WindowBackgroundBrush}" 
      BorderBrush="{StaticResource SolidBorderBrush}" 
      BorderThickness="1" Padding="1" >
    <Grid x:Name="root">
      <DockPanel x:Name="dockWarning" 
              Visibility="Collapsed"  LastChildFill="False" >
      <Border DockPanel.Dock="Right"  BorderBrush="Red" Background="Red" 
          BorderThickness="2"  CornerRadius="2,2,0,0">
        <TextBlock x:Name="txtWarning" DockPanel.Dock="Right" 
                   Text="{TemplateBinding ToolTip}" VerticalAlignment="Top" 
                   Background="Red" Foreground="White"  FontSize="10" />
            <TranslateTransform X="2" Y="{Binding ElementName=dockWarning, 
                              Converter={x:Static t:InvertSignConverter.Instance}}"/>
            <!--TranslateTransform move the border to 
                   upper right corner, outside the TextBox -->
            <!--InvertSignConverter is a IValueConverter 
                   that change + to -, - to + -->
        <Condition Property="Validation.HasError" Value="true" />
        <Condition SourceName="PART_Popup" Property="IsOpen" Value="False" />
      <Setter Property="ToolTip" Value="{Binding 
               RelativeSource={RelativeSource Self}, 
      <Setter TargetName="dockWarning" Property="Visibility" Value="Visible" />
      <Setter TargetName="Border" Property="BorderThickness" Value="2" />
      <Setter TargetName="Border" Property="Padding" Value="0" />
      <Setter TargetName="Border" Property="BorderBrush" Value="Red" />


  • 22-12-08: Initial version.
  • 25-12-08: Added ghost image when picking from ItemList.
  • 25-12-08: Handles PageUp/Down/Up buttons in the textbox.
  • 25-12-08: Handles Escape button in the ItemList.
  • 25-12-08: Disabled the caching system as it doesn't work well.
  • 25-12-08: Updated version 1.
  • 04-09-09: Disabled the TempVisual component.
  • 04-09-09: Popup repositions automatically when window is moved.
  • 04-09-09: Popup hides when deactivated (restores when activated).
  • 04-09-09: Updated version 2.


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


About the Author

You may also be interested in...


Comments and Discussions

GeneralSource Code Pin
GerhardKreuzer18-Jan-09 20:53
memberGerhardKreuzer18-Jan-09 20:53 
GeneralRe: Source Code Pin
Leung Yat Chun18-Jan-09 23:21
memberLeung Yat Chun18-Jan-09 23:21 
GeneralRe: Source Code Pin
Kharcoff19-Nov-09 19:35
memberKharcoff19-Nov-09 19:35 
GeneralMy vote of 1 Pin
John Simmons / outlaw programmer22-Dec-08 0:47
mvpJohn Simmons / outlaw programmer22-Dec-08 0:47 

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

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

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170215.1 | Last Updated 4 Sep 2009
Article Copyright 2008 by Leung Yat Chun
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid