How to Implement a ComboBox with Custom Filtering

RogerSchlueter-7899 1,196 Reputation points
2022-10-28T01:25:19.523+00:00

I am trying to implement a filtered ComboBox that is populated with a list of people. Here is the definition of the ComboBox:

<Window.Resources>  
    <CollectionViewSource x:Key="cvsPeople" />  
	....  
</Window.Resources>  
....  
<ComboBox  
    x:Name="cbxPeople"  
    DisplayMemberPath="DisplayName"  
    IsEditable="True"  
    IsReadOnly="False"  
    IsSynchronizedWithCurrentItem="True"  
    ItemsSource="{Binding}"  
    Width="150" />  
....  

Here is the code that runs when the window is loaded:

Privite Sub Loaded Handles Me.Loaded  
    cvsPeople = DirectCast(Resources("cvsPeople"), CollectionViewSource)  
    cvsPeople.Source = ocPeople  
    DataContext = cvsPeople  
....  
    pup = DirectCast(cbxPeople.Template.FindName("PART_Popup", cbxPeople), Popup)  
    tb = DirectCast(cbxPeople.Template.FindName("PART_EditableTextBox", cbxPeople), Controls.TextBox)  
    AddHandler tb.KeyUp, AddressOf Filter  
End Sub  

Here is the filtering subroutine:

Private Sub Filter(sender As Object, e As KeyEventArgs)  
		Static FilteredPeeps As ObservableCollection(Of Person)  
		If FilteredPeeps Is Nothing Then FilteredPeeps = ocPeople  
		Dim Searchstring As String = cbxPeople.Text  
		If Searchstring.Length = 0 Then  
			FilteredPeeps = ocPeople  
		Else  
			FilteredPeeps = PeopleFilter(FilteredPeeps, Searchstring)  
			Select Case FilteredPeeps.Count  
				Case 0  
					FilteredPeeps = ocPeople  
				Case 1  
					clsCP = FilteredPeeps.Item(0)  
					clsCP.IsDirty = False  
					clsCP.LastUpdate = Date.Now.Date  
					RemoveHandler cbxPeople.SelectionChanged, AddressOf SelectPerson  
					cbxPeople.SelectedIndex = 0  
					AddHandler cbxPeople.SelectionChanged, AddressOf SelectPerson  
					PopulateWindow()  
				Case Else  
					cbxPeople.ItemsSource = FilteredPeeps  
					pup.IsOpen = True  
					cbxPeople.Text = Searchstring  
					tb = DirectCast(cbxPeople.Template.FindName("PART_EditableTextBox", cbxPeople), Controls.TextBox)  
					tb.SelectionStart = Searchstring.Length  
			End Select  
		End If  
	End Sub  

where
-- ocPeople is an ObservableCollection(Of Person) which is a collection of all the people in the database.
-- clsdCP is intended to be the current person, the one selected in the ComboBox
-- PopulateWindow is just a subroutine that does additional processing of the selected person.

The actual filtering is done with this subroutine:

Private Function PeopleFilter(FilteredPeeps As IEnumerable(Of Person), Searchstring As String) As ObservableCollection(Of Person)  
    Dim peeps As New ObservableCollection(Of Person)  
    For Each p As Person In FilteredPeeps  
        If p.DisplayName.Contains(Searchstring, StringComparison.OrdinalIgnoreCase) Then peeps.Add(p)  
    Next  
    Return peeps  
End Function  

This does not work:
-- After filtering, the ComboBox does not respond to clicks
-- When "Enter" is the key that was pressed, the selected person is NOT selected
-- When only one person is left in the ComboBox that person is NOT selected

Regarding the last point: when a person is selected by clicing in the drop down list, the CurrentItem of the collection view is set to the selected person. However, when the person is selected programatically, for example by setting cbxPeople.SelectedIndexx = 0, the CurrentItem of the collection view is NOT set. That doesn't seen right to me.

Surely I am not the first person to implement a filtered ComboBox but all my searches have not yielded anything.

Please point me to a working example or suggest improvements to me code.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,669 questions
VB
VB
An object-oriented programming language developed by Microsoft that is implemented on the .NET Framework. Previously known as Visual Basic .NET.
2,568 questions
{count} vote

Accepted answer
  1. Hui Liu-MSFT 38,191 Reputation points Microsoft Vendor
    2022-11-03T03:07:08.407+00:00

    Your idea is great, after my test it works. Here are the steps.

    1.Create the WPF Custom Control Library named WpfCustomControlLibrary2 .
    256509-image.png
    2.Right Click CustomControl1.cs and selected Rename , then edit the class named MyComboBox .
    256557-image.png

    3.Complete code for WpfCustomControlLibrary2 .

    You could add modifier public to MyComboBox

    class MyComboBox : DependencyObject  --->  public class MyComboBox : DependencyObject  
    

    Code.

    256621-mycomboboxlibrary.txt

    4.Add reference WpfCustomControlLibrary2(dll) for VBproject.
    256576-image.png
    5.VB project:
    MainWindow.xaml:

    256510-image.png

    MainWindow.vb:

    Imports System.Runtime.CompilerServices  
    Imports System.ComponentModel  
    Imports System.Collections.ObjectModel  
      
    Partial Public Class MainWindow  
        Inherits Window  
      
        Public Sub New()  
            InitializeComponent()  
            Dim vm As FilterViewModel = New FilterViewModel()  
            Me.DataContext = vm  
        End Sub  
    End Class  
      
    Public Class FilterViewModel  
        Implements INotifyPropertyChanged  
      
        Private _dataSource As ObservableCollection(Of String)  
      
        Public Property DataSource As ObservableCollection(Of String)  
            Get  
                Return _dataSource  
            End Get  
            Set(ByVal value As ObservableCollection(Of String))  
                _dataSource = value  
                OnPropertyChanged("ListOfCountry")  
            End Set  
        End Property  
      
        Public Sub New()  
            _dataSource = New ObservableCollection(Of String)()  
            _dataSource.Add("Beijing")  
            _dataSource.Add("New York")  
            _dataSource.Add("Paris")  
            _dataSource.Add("Shanghai")  
            _dataSource.Add("London")  
            _dataSource.Add("Nanjing")  
        End Sub  
      
        Private Event INotifyPropertyChanged_PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged  
      
        Friend Sub OnPropertyChanged(<CallerMemberName> ByVal Optional propName As String = "")  
      
            RaiseEvent INotifyPropertyChanged_PropertyChanged(Me, New PropertyChangedEventArgs(propName))  
      
        End Sub  
    End Class  
    

    The result:
    256587-image.png

    ----------------------------------------------------------------------------

    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


0 additional answers

Sort by: Most helpful