Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

custom search for combobox

I am creating a WPF app containing a ComboBox which shows some data. I want to use the combobox-integrated text seach. But the problem is, if the user searchs for "llo", the list should show all items, containing this text snippet, like "Hallo", "Hello", "Rollo" ... But the search returns no result because the property name of no item starts with "llo". Has somebody an idea how to achieve this?

I am using the MVVM-pattern. The view is binded to a collection of DTOs (property of the viewmodel), in the DTO there are two properties which are relevant for the search.

        <ComboBox 
            ItemsSource="{Binding Path=Agencies}"
            SelectedItem="{Binding Path=SelectedAgency}"
            IsTextSearchEnabled="True"
            DisplayMemberPath="ComboText"
            IsEnabled="{Binding IsReady}"
            IsEditable="True"
            Grid.Column="0"
            Grid.Row="0" 
            IsTextSearchCaseSensitive="False"
            HorizontalAlignment="Stretch">
        </ComboBox>

    public class Agency
    {
        public int AgencyNumber { get; set; }
        public string Title { get; set; }
        public string Name { get; set; }
        public string ContactPerson { get; set; }
        public string ComboText => $"{this.AgencyNumber}\t{this.Name}";
    }
like image 846
Chris Crozz Avatar asked Oct 25 '25 15:10

Chris Crozz


1 Answers

Ginger Ninja | Kelly | Diederik Krols definitely provide a nice all in one solution, but it may be a tad on the heavy side for simple use cases. For example, the derived ComboBox gets a reference to the internal editable textbox. As Diederik points out "We need this to get access to the Selection.". Which may not be a requirement at all. Instead we could simply bind to the Text property.

<ComboBox 
    ItemsSource="{Binding Agencies}"
    SelectedItem="{Binding SelectedAgency}"
    Text="{Binding SearchText}"
    IsTextSearchEnabled="False" 
    DisplayMemberPath="ComboText"
    IsEditable="True" 
    StaysOpenOnEdit="True"
    MinWidth="200" />

Another possible improvement is to expose the filter, so devs could easily change it. Turns out this can all be accomplished from the viewmodel. To keep things interesting I chose to use the Agency's ComboText property for DisplayMemberPath, but its Name property for the custom filter. You could, of course, tweak this however you like.

public class MainViewModel : ViewModelBase
{
    private readonly ObservableCollection<Agency> _agencies;

    public MainViewModel()
    {
        _agencies = GetAgencies();
        Agencies = (CollectionView)new CollectionViewSource { Source = _agencies }.View;
        Agencies.Filter = DropDownFilter;
    }

    #region ComboBox

    public CollectionView Agencies { get; } 

    private Agency selectedAgency;
    public Agency SelectedAgency
    {
        get { return selectedAgency; }
        set
        {
            if (value != null)
            {
                selectedAgency = value;
                OnPropertyChanged();
                SearchText = selectedAgency.ComboText;
            }
        }
    }

    private string searchText;
    public string SearchText
    {
        get { return searchText; }
        set
        {
            if (value != null)
            {
                searchText = value;
                OnPropertyChanged();
                if(searchText != SelectedAgency.ComboText) Agencies.Refresh();
            }
        }
    }

    private bool DropDownFilter(object item)
    {
        var agency = item as Agency;
        if (agency == null) return false;

        // No filter
        if (string.IsNullOrEmpty(SearchText)) return true;
        // Filtered prop here is Name != DisplayMemberPath ComboText
        return agency.Name.ToLower().Contains(SearchText.ToLower());
    }

    #endregion ComboBox

    private static ObservableCollection<Agency> GetAgencies()
    {
        var agencies = new ObservableCollection<Agency>
        {
            new Agency { AgencyNumber = 1, Name = "Foo", Title = "A" },
            new Agency { AgencyNumber = 2, Name = "Bar", Title = "C" },
            new Agency { AgencyNumber = 3, Name = "Elo", Title = "B" },
            new Agency { AgencyNumber = 4, Name = "Baz", Title = "D" },
            new Agency { AgencyNumber = 5, Name = "Hello", Title = "E" },
        };
        return agencies;
    }
}

The main gotchas:

  • When the user enters a search and then selects an item from the filtered list, we want SearchText to be updated accordingly.
  • When this happens, we don't want to refresh the filter. For this demo, we're using a different property for DisplayMemberPath and our custom filter. So if we would let the filter refresh, the filtered list would be empty (no matches are found) and the selected item would be cleared as well.

On a final note, if you specify the ComboBox's ItemTemplate, you'll want to set TextSearch.TextPath instead of DisplayMemberPath.

like image 128
Funk Avatar answered Oct 28 '25 05:10

Funk