Click here to Skip to main content
14,428,333 members

WPF AutoComplete Folder TextBox

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

Image 1


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

Leung Yat Chun (Fainx)
Founder FANIX.ME
Hong Kong Hong Kong
From the end of the Universe we have Fainx and Quick Zip.
http://www.FANIX.ME is stolen by and moved to
May Thy Lord Your God be blessed always.

DirectoryInfoEx - [1.0.27]
WPF FileExplorer3 - [3.0.19]
WPF HtmlTextBlock - [Codeplex]
WPF ListView MultiSelect - [0.4]
WPF UIEventHub MultiSelect/DragDrop w Touch support - [3.0]
WPF BreadcrumbFolderTextBox - [2.5]
WPF BreadcrumbTree and Breadcrumb
WPF Aero Titlebar - [0.2]

Comments and Discussions

GeneralMy vote of 4 Pin
Anna Novikova2-May-12 1:09
professionalAnna Novikova2-May-12 1:09 
GeneralMore Windows-Like behaviour Pin
TEiseler24-Apr-11 10:16
MemberTEiseler24-Apr-11 10:16 
GeneralSolution: Repositioning of the Popup for the AutoCompleteBox Pin
SilverLaw3-Sep-09 21:49
MemberSilverLaw3-Sep-09 21:49 
GeneralRe: Solution: Repositioning of the Popup for the AutoCompleteBox [modified] Pin
Leung Yat Chun (Fainx)3-Sep-09 23:30
professionalLeung Yat Chun (Fainx)3-Sep-09 23:30 
GeneralRe: Solution: Repositioning of the Popup for the AutoCompleteBox Pin
Leung Yat Chun (Fainx)4-Sep-09 0:00
professionalLeung Yat Chun (Fainx)4-Sep-09 0:00 
GeneralRe: Solution: Repositioning of the Popup for the AutoCompleteBox Pin
SilverLaw4-Sep-09 12:15
MemberSilverLaw4-Sep-09 12:15 
GeneralPopup dont move with main window Pin
GerhardKreuzer19-Jan-09 1:55
MemberGerhardKreuzer19-Jan-09 1:55 
AnswerRe: Popup dont move with main window [modified] Pin
Leung Yat Chun (Fainx)19-Jan-09 2:39
professionalLeung Yat Chun (Fainx)19-Jan-09 2:39 
GeneralRe: Popup dont move with main window Pin
GerhardKreuzer19-Jan-09 2:49
MemberGerhardKreuzer19-Jan-09 2:49 
GeneralRe: Popup dont move with main window Pin
Leung Yat Chun (Fainx)20-Jan-09 8:03
professionalLeung Yat Chun (Fainx)20-Jan-09 8:03 
GeneralSource Code Pin
GerhardKreuzer18-Jan-09 20:53
MemberGerhardKreuzer18-Jan-09 20:53 
GeneralRe: Source Code Pin
Leung Yat Chun (Fainx)18-Jan-09 23:21
professionalLeung Yat Chun (Fainx)18-Jan-09 23:21 
GeneralRe: Source Code Pin
Kharcoff19-Nov-09 19:35
MemberKharcoff19-Nov-09 19:35 
GeneralMy vote of 1 Pin
#realJSOP22-Dec-08 0:47
mva#realJSOP22-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.

Posted 21 Dec 2008


34 bookmarked