Hi,
I implemented an Auto-complete textbox by using a ListBox.
When the textbox 'tbSearch' text is changed the ListBox will automatically show Names that contains this search string as substring (irrespective of the case)
At the same time a comboxbox will show 'Address|Name' where it contains the search Name (again as substring is enough)
Now, if I change tbSearch text at normal speed adding characters or deleting (backspace) or combination, the listBoxName and comboBoxAddressName update almost immediately,
"UNTIL" I choose an item from the combobox list. There are no event handlers on combobox selection changed event.
From the time the combobox is 'touched' the tbSearch text response gets very slow. At the same keystroke speed the characters now appear delayed in the search box.
I used VirtualizingStackPanel and set IsVirtualizing="True" and VirtualizationMode="Recycling" on the ListBox and ComboBox. I am using Visual C# 2010 Express.
If someone can test run the below code and confirm they have the same experience and suggest tuning?
How to test:
To test, enter keystrokes continuously in the textbox with backspaces in between, such that the text content and length varies and length is in the range between 1-6 characters. Increase the keystroke speed too. Now stop entering text, and select an item
from the comboxbox. Now edit textbox text in the same way and notice the UI response.
(FYI: I used random strings which are all uppercase, my actual data will be mixed case, incase you are wondering why I am converting list and string to lowercase before comparing)
<Window x:Class="WpfAppUIPerf.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="241" Width="739"><Grid><GroupBox Margin="2,2,2,2"><Grid Margin="2,2,2,2"><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"></ColumnDefinition><ColumnDefinition Width="Auto"></ColumnDefinition></Grid.ColumnDefinitions><Grid Grid.Row="0" Grid.Column="0" Margin="2,2,2,2"><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition Height="*"></RowDefinition></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"></ColumnDefinition><ColumnDefinition Width="Auto"></ColumnDefinition></Grid.ColumnDefinitions><Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right">Search</Label><TextBox Name="tbSearch" Grid.Row="0" Grid.Column="1" Margin="2,0,2,0" MinWidth="100" HorizontalAlignment="Left" TextChanged="tbSearch_TextChanged" PreviewKeyDown="tbSearch_PreviewKeyDown"></TextBox><Label Name="lblNameCount" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top"></Label><ListBox Name="listBoxName" Grid.Row="1" Grid.Column="1" Height="100" Margin="2,0,2,0" MinWidth="100" HorizontalAlignment="Left" Visibility="Collapsed" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" Background="LightYellow" SelectionChanged="listBoxName_SelectionChanged" MouseDoubleClick="listBoxName_MouseDoubleClick"></ListBox></Grid><Grid Grid.Row="0" Grid.Column="1" Margin="2,2,2,2"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"></ColumnDefinition><ColumnDefinition Width="Auto"></ColumnDefinition></Grid.ColumnDefinitions><ComboBox Name="comboBoxAddressName" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Top" FontFamily="Courier New" MinWidth="300" Margin="2,0,0,0" MaxDropDownHeight="100" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" IsReadOnly="True"></ComboBox><Label Name="lblAddNameCount" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top"></Label></Grid></Grid></GroupBox></Grid></Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfAppUIPerf { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { List<string> NameList = new List<string>(); List<string> AddressNameList = new List<string>(); public MainWindow() { InitializeComponent(); initalize_lists(); } private void initalize_lists() { int count = 0; while (count < 20000) { // get 1st random string string strName = RandomString(8); // get 2nd random string string strAddress = RandomString(40); // creat full rand string string strAddName = strAddress + "|" + strName; if (AddressNameList.Contains(strAddName)) continue; NameList.Add(strName); AddressNameList.Add(strAddName); count++; } } private static Random random = new Random((int)DateTime.Now.Ticks);//thanks to McAden private string RandomString(int size) { StringBuilder builder = new StringBuilder(); char ch; for (int i = 0; i < size; i++) { ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); builder.Append(ch); } return builder.ToString(); } private void tbSearch_TextChanged(object sender, TextChangedEventArgs e) { if (tbSearch.Text.Length <= 0) { listBoxName.ItemsSource = null; listBoxName.Visibility = Visibility.Collapsed; lblNameCount.Content = "0"; lblNameCount.Visibility = Visibility.Collapsed; comboBoxAddressName.ItemsSource = null; lblAddNameCount.Content = "0"; lblAddNameCount.Visibility = Visibility.Collapsed; return; } IEnumerable<string> listBoxNameResults = NameList.Where( delegate(string s) { return s.ToLower().Contains(tbSearch.Text.ToLower()); }); //MessageBox.Show(listBoxNameResults.Count().ToString(), tbSearch.Text); IEnumerable<string> comboBoxAddressNameResults = AddressNameList.Where( delegate(string s) { return (s.Substring(s.LastIndexOf("|")).ToLower()).Contains(tbSearch.Text.ToLower()); }); if (listBoxNameResults.Count() > 0) { listBoxName.ItemsSource = listBoxNameResults; listBoxName.Visibility = Visibility.Visible; lblNameCount.Content = listBoxNameResults.Count().ToString(); lblNameCount.Visibility = Visibility.Visible; comboBoxAddressName.ItemsSource = comboBoxAddressNameResults; lblAddNameCount.Content = comboBoxAddressNameResults.Count().ToString(); lblAddNameCount.Visibility = Visibility.Visible; if (comboBoxAddressNameResults.Count() > 0) comboBoxAddressName.SelectedIndex = 0; } else { listBoxName.ItemsSource = null; listBoxName.Visibility = Visibility.Collapsed; lblNameCount.Content = "0"; lblNameCount.Visibility = Visibility.Collapsed; comboBoxAddressName.ItemsSource = null; lblAddNameCount.Content = "0"; lblAddNameCount.Visibility = Visibility.Collapsed; } } private void tbSearch_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Down) { if (listBoxName.SelectedIndex < listBoxName.Items.Count) { listBoxName.SelectedIndex = listBoxName.SelectedIndex + 1; } } if (e.Key == Key.Up) { if (listBoxName.SelectedIndex > -1) { listBoxName.SelectedIndex = listBoxName.SelectedIndex - 1; } } if (e.Key == Key.Enter || e.Key == Key.Tab) { // Commit the selection listBoxName.Visibility = Visibility.Collapsed; e.Handled = (e.Key == Key.Enter); } if (e.Key == Key.Escape) { // Cancel the selection listBoxName.ItemsSource = null; listBoxName.Visibility = Visibility.Collapsed; } } private void listBoxName_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (listBoxName.ItemsSource == null || listBoxName.SelectedIndex < 0) return; tbSearch.TextChanged -= new TextChangedEventHandler(this.tbSearch_TextChanged); tbSearch.Text = listBoxName.SelectedItem.ToString(); IEnumerable<string> comboBoxAddressNameResults = AddressNameList.Where( delegate(string s) { return (s.Substring(s.LastIndexOf("|")).ToLower()).Contains(tbSearch.Text.ToLower()); });
comboBoxAddressName.ItemsSource= comboBoxAddressNameResults; if (comboBoxAddressNameResults.Count() > 0) comboBoxAddressName.SelectedIndex = 0; tbSearch.TextChanged += new TextChangedEventHandler(this.tbSearch_TextChanged); } private void listBoxName_MouseDoubleClick(object sender, MouseButtonEventArgs e) { listBoxName.Visibility = Visibility.Collapsed; lblNameCount.Visibility = Visibility.Collapsed; if (comboBoxAddressName.Items.Count > 1) { comboBoxAddressName.IsDropDownOpen = true; } } } }
Debug output:
'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.HostingProcess.Utilities\10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.HostingProcess.Utilities.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Drawing.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.HostingProcess.Utilities.Sync\10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.HostingProcess.Utilities.Sync.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\assembly\GAC_MSIL\Microsoft.VisualStudio.Debugger.Runtime\10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.Debugger.Runtime.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Users\sri\Documents\Visual Studio 2010\Projects\WpfAppUIPerf\WpfAppUIPerf\bin\Debug\WpfAppUIPerf.vshost.exe', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.CSharp\v4.0_4.0.0.0__b03f5f7f11d50a3a\Microsoft.CSharp.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Data.DataSetExtensions\v4.0_4.0.0.0__b77a5c561934e089\System.Data.DataSetExtensions.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Xaml.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_32\PresentationCore\v4.0_4.0.0.0__31bf3856ad364e35\PresentationCore.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. The thread 'vshost.NotifyLoad' (0x10e8) has exited with code 0 (0x0). The thread '<No Name>' (0x1ecc) has exited with code 0 (0x0). The thread 'vshost.LoadReference' (0x1444) has exited with code 0 (0x0). 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Users\sri\Documents\Visual Studio 2010\Projects\WpfAppUIPerf\WpfAppUIPerf\bin\Debug\WpfAppUIPerf.exe', Symbols loaded. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. Step into: Stepping over non-user code 'WpfAppUIPerf.App.App' Step into: Stepping over non-user code 'WpfAppUIPerf.App.InitializeComponent' 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework.Aero\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.Aero.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\UIAutomationProvider\v4.0_4.0.0.0__31bf3856ad364e35\UIAutomationProvider.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. 'WpfAppUIPerf.vshost.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Accessibility\v4.0_4.0.0.0__b03f5f7f11d50a3a\Accessibility.dll' The thread '<No Name>' (0x2064) has exited with code 0 (0x0). The thread 'vshost.RunParkingWindow' (0x2220) has exited with code 0 (0x0). The thread '<No Name>' (0x21b8) has exited with code 0 (0x0). The program '[8860] WpfAppUIPerf.vshost.exe: Managed (v4.0.30319)' has exited with code 0 (0x0).
Thanks,
-srinivas y.
sri