target audience

Written by

in

Building a custom AutoComplete TextBox in WPF requires combining a TextBox for user input and a floating Popup containing a ListBox to display the filtered suggestions. WPF does not include a native standalone autocomplete text control out of the box, so creating a reusable UserControl is the cleanest approach.

This implementation covers the XAML layout, C# code-behind with a dependency property, and data filtering logic. 1. The XAML Layout (AutoCompleteTextBox.xaml)

The layout embeds a Popup layout directly underneath the TextBox. The Popup uses PlacementTarget to track the position of the TextBox and automatically sizes itself to match.

Use code with caution. 2. The Code-Behind (AutoCompleteTextBox.xaml.cs)

The code-behind needs to expose a DependencyProperty so developers can bind a list of strings from their ViewModels. It also manages text filtering and keyboard navigation.

using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WpfApp.Controls { public partial class AutoCompleteTextBox : UserControl { public AutoCompleteTextBox() { InitializeComponent(); } // Dependency Property to allow item binding from external sources public static readonly DependencyProperty SuggestionsSourceProperty = DependencyProperty.Register( nameof(SuggestionsSource), typeof(IEnumerable), typeof(AutoCompleteTextBox), new PropertyMetadata(null)); public IEnumerable SuggestionsSource { get => (IEnumerable)GetValue(SuggestionsSourceProperty); set => SetValue(SuggestionsSourceProperty, value); } // Triggered whenever the user types into the box private void TxtInput_TextChanged(object sender, TextChangedEventArgs e) { string query = txtInput.Text; // Hide popup if the input is empty or source data is missing if (string.IsNullOrWhiteSpace(query) || SuggestionsSource == null) { popupSuggestions.IsOpen = false; return; } // Filter data source (case-insensitive contains) var filtered = SuggestionsSource.Cast() .Where(item => item.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) .ToList(); if (filtered.Any()) { lbSuggestions.ItemsSource = filtered; popupSuggestions.IsOpen = true; } else { popupSuggestions.IsOpen = false; } } // Triggered when an item is selected from the popup list private void LbSuggestions_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (lbSuggestions.SelectedItem != null) { // Temporarily detach event handler to prevent infinite filter loop txtInput.TextChanged -= TxtInput_TextChanged; txtInput.Text = lbSuggestions.SelectedItem.ToString(); txtInput.CaretIndex = txtInput.Text.Length; // Move cursor to the end popupSuggestions.IsOpen = false; txtInput.TextChanged += TxtInput_TextChanged; txtInput.Focus(); } } // Enables keyboard navigation (Down Arrow key drops focus into list) private void TxtInput_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Down && popupSuggestions.IsOpen) { lbSuggestions.Focus(); if (lbSuggestions.Items.Count > 0) { lbSuggestions.SelectedIndex = 0; } } } } } Use code with caution. 3. How to Consume Your Custom Control

Once built, add the local namespace to your target Window/Page XAML and pass your data source directly to the SuggestionsSource property.

Use code with caution. Architectural Upgrades for Production Use

If you need a highly responsive or complex control, scale this pattern using the following techniques:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *