WPF combobox textsearch with contains
Asked Answered
C

6

7

How can i implement my Combobox TextSearch using contains instead of StartsWith

<rf:ComboBox Grid.Row="1"
                         Grid.Column="5"
                         Width="200"
                         ItemsSource="{Binding Source={StaticResource AccountProvider}}"
                         DisplayMemberPath="Description"
                         SelectedValuePath="IndRekId"
                         IsEmptyItemVisible="True"
                         SelectedValue="{Binding Id, UpdateSourceTrigger=PropertyChanged}"
                         IsTextSearchEnabled="True"
                         TextSearch.TextPath="Description"
                         IsEditable="True"/>

The search function works but i need to match on substrings

Chanellechaney answered 15/3, 2013 at 8:0 Comment(1)
As far as I know the only way to accomplish this is to create a control that extends ComboBox and add the functionality that you need.Bice
C
6

Here I have an example in MVVM framework.

my xaml file:

<ComboBox Name="cmbContains" IsEditable="True" IsTextSearchEnabled="false" ItemsSource="{Binding pData}"  DisplayMemberPath="wTitle" Text="{Binding SearchText ,Mode=TwoWay}"  >
  <ComboBox.Triggers>
      <EventTrigger RoutedEvent="TextBoxBase.TextChanged">
          <BeginStoryboard>
              <Storyboard>
                  <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsDropDownOpen">
                      <DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/>
                  </BooleanAnimationUsingKeyFrames>
              </Storyboard>
          </BeginStoryboard>
      </EventTrigger>
  </ComboBox.Triggers>
</ComboBox>

my cs file:

//ItemsSource - pData
//There is a string attribute - wTitle included in the fooClass (DisplayMemberPath)
private ObservableCollection<fooClass> __pData;
public ObservableCollection<fooClass> pData {
    get { return __pData; }
    set { Set(() => pData, ref __pData, value);
        RaisePropertyChanged("pData");
    }
}

private string _SearchText;
public string SearchText {
    get { return this._SearchText; }
    set {
        this._SearchText = value;
        RaisePropertyChanged("SearchText");

        //Update your ItemsSource here with Linq
        pData = new ObservableCollection<fooClass>{pData.ToList().Where(.....)};
    }
}

You can see the editable comboBox is binding to the string (SearchText) Once there is a TextChanged event the drop down is shown and the Two way binding update the value. The ItemsSource changed in the cs file while it goes into the set{}; syntax.

https://gist.github.com/tonywump/82e66abaf71f715c4bd45a82fce14d80

Comeback answered 7/7, 2016 at 14:27 Comment(0)
M
3

This sample look like "TextSearch"

In XAML file you should add only one attribute to combobox "TextContainSearch.Text":

<ComboBox ItemsSource="{Binding Model.formListIntDeviceNumbers}" SelectedItem="{Binding Path=Model.selectedDeviceNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="DeviceNumber" IsEditable="True" c:TextContainSearch.Text="DeviceNumber">

And we should add using in the header of XAML file:

xmlns:c="clr-namespace:Adaptive.Controls.Extension"

And C# code in *.cs file:

using System;
using System.Windows;
using System.Windows.Controls;
namespace Adaptive.Controls.Extension
{
 public sealed class TextContainSearch : DependencyObject {
        public static void SetText(DependencyObject element, string text) {
            var controlSearch = element as Control;
            if (controlSearch != null)
                controlSearch.KeyUp += (sender, e) =>
                {
                    if (sender is ComboBox){
                        var control = sender as ComboBox;
                        control.IsDropDownOpen = true;
                        var oldText = control.Text;
                        foreach(var itemFromSource in control.ItemsSource){
                            if (itemFromSource != null)
                            {
                                Object simpleType = itemFromSource.GetType().GetProperty(text).GetValue(itemFromSource, null);
                                String propertOfList = simpleType as string;
                                if (!string.IsNullOrEmpty(propertOfList) && propertOfList.Contains(control.Text))
                                {
                                    control.SelectedItem = itemFromSource;
                                    control.Items.MoveCurrentTo(itemFromSource);
                                    break;
                                }
                            }
                        }
                        control.Text = oldText;
                        TextBox txt = control.Template.FindName("PART_EditableTextBox", control) as TextBox;
                        if (txt != null)
                        {
                            txt.Select(txt.Text.Length, 0);
                        }
                    }
                };
        }
    }
}
Mooned answered 14/9, 2017 at 8:19 Comment(2)
What's the pattern in this snippet called? I know attached properties and use them most of the time for what this snippet does - registering to an event of a control. Though i have never seen this and am very interested in how your approach works.Cheshvan
Hi @Mooned , But inSetText(DependencyObject element, string text) , the value of "text" parameter is always "DeviceNumber" string. So my typed text is not reflecting there. Any reason?Medea
T
3

Try this:

 <ComboBox Padding="3,5" MinWidth="150" SelectedItem="{Binding NewBoxRequest}"
 ItemsSource="{Binding Requests}" DisplayMemberPath="SN" IsEditable="True"
 StaysOpenOnEdit="True"
 Text="{Binding SnFilter,UpdateSourceTrigger=PropertyChanged}">
 </ComboBox>

view model:

    private string snFilter;

    public string SnFilter
    {
        get { return snFilter; }
        set
        {
            snFilter = value;
            RaisePropertyChanged();
            RaisePropertyChanged(nameof(Requests));
        }
    }
    private List<Request> requests;

    public List<Request> Requests
    {
        get => string.IsNullOrEmpty(SnFilter) || requests.Any(r => r.SN == SnFilter)
            ? requests
            : requests.Where(r => r.SN.Contains(SnFilter)).ToList();
        set
        {
            requests = value;
            RaisePropertyChanged();
        }
    }
Treed answered 15/5, 2019 at 8:24 Comment(2)
This is the simplest - I'm not sure what the requests.Any() condition is for, I didn't include it and haven't noticed any issuesSimper
The requests.Any() condition is for displaying all items when the combobox being clicked again with a filter, if you do not want this you can remove it.Treed
S
2

There is no way to replace string.StartsWith() with string.Contains(). You have to write your custom ComboBox.

This article may help you: http://www.codeproject.com/Tips/631196/ComboBox-with-Suggest-Ability-based-on-Substring-S

Sweitzer answered 17/3, 2014 at 10:16 Comment(0)
W
1

I could not get the "Set" syntax to work in my C# system, so here is a small takeoff to Wu's answer above (this is in a custom control):

 <ComboBox IsEditable="True" 
      IsTextSearchEnabled="false" 
      ItemsSource="{Binding pData, RelativeSource = {RelativeSource TemplatedParent}}"  
      DisplayMemberPath="description" 
      Text="{Binding SearchText , RelativeSource = {RelativeSource TemplatedParent}, Mode=TwoWay}"  >
     <ComboBox.Triggers>
                                    <EventTrigger RoutedEvent="TextBoxBase.TextChanged">
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsDropDownOpen">
                                                    <DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/>
                                                </BooleanAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </EventTrigger>
                                </ComboBox.Triggers>
                            </ComboBox>

In the custom control:

private async void _Loaded(object sender, RoutedEventArgs e)
        {
            var n = await InitializeLabTests;

            allTests = new ObservableCollection<CommonProcedure>(n);
            pData = new ObservableCollection<CommonProcedure>(n);
        }

//ItemsSource - pData
        //There is a string attribute - wTitle included in the fooClass (DisplayMemberPath)
        private ObservableCollection<CommonProcedure> __pData;
        public ObservableCollection<CommonProcedure> pData
        {
            get { return __pData; }
            set { __pData = value; RaisePropertyChanged(); }
        }

        private string _SearchText;
        public string SearchText
        {
            get { return _SearchText; }
            set
            {
                _SearchText = value; RaisePropertyChanged();

                //Update your ItemsSource here with Linq
                pData = new ObservableCollection<CommonProcedure>
               (
                    allTests.Where(q => q.description.Contains(SearchText))
               );
            }
        }

The only significant difference being in the SearchText setter.

Womb answered 27/6, 2019 at 22:46 Comment(0)
D
0

How about a dynamic event on ComboBox KeyUp event:

 <ComboBox DisplayMemberPath="Label" Height="37" Padding="5"                                     
           SelectedValue="{Binding Client, UpdateSourceTrigger=PropertyChanged}"
           ItemsSource="{Binding ClientList, UpdateSourceTrigger=PropertyChanged }" IsEditable="True"  TextSearch.TextPath="Label"
           KeyUp="ComboBox_KeyUp"
 />

Event:

private void ComboBox_KeyUp(object sender, KeyEventArgs e)
{
    var control = sender as ComboBox;

    if (control == null) { return; }

    if (control.Tag == null)
    {
        control.Tag = control.ItemsSource as List<ComboBoxItemViewModel>;
    }

    var filter = control.Text.ToLower();
    if (filter.Length == 0)
    {
        // If no text, show all items
        control.ItemsSource = control.Tag as List<ComboBoxItemViewModel>;
    }
    else
    {
        var dataSource = control.Tag as List<ComboBoxItemViewModel>;

        if (dataSource == null) return;

        // Filter items based on the text entered
        control.ItemsSource = dataSource.Where(item => item.Label.ToLower().Contains(filter)).ToList();
    }
}

//Just replace the List<ComboBoxItemViewModel> with your ViewModel
Desensitize answered 11/4 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.