简体   繁体   中英

Not an autocomplete WPF Combobox (but maybe close)

I have an ObservableCollection and a ComboBox binded to it. What I want to achieve is to filter that ObservableCollection and only let the ComboBox display the filtered items. Later somewhere I have a foreach (item in ComboBox) loop . The filtering should be by writing some letters, and if the name property of the items of that are in ObservableCollection doesn't contain that letters, remove that item.

I know there is a way of typing directly in the ComboBox with IsEditable property, but for this example let's just use an additional TextBox for the user's input.

For practice I'm doing it with a ObservableCollection<string> (and not with a my <myClass> that has more properties.)

public MainWindow()
    {

        InitializeComponent();

        names= new ObservableCollection<string>();

        names.Add("Harry");
        names.Add("Ron");
        names.Add("Einstein");
        names.Add("Frodo");
        names.Add("Spiderman");

        myComboBox.DataContext = this;
    }

         public ObservableCollection<string> names{ get; set; }
         public ObservableCollection<string> filteredNames{ get; set; }

I've created this method:

 void toFilter()
      {
      filteredNames=new ObservableCollection<string>(names.Where(name=> name.StartsWith(myTextBox.Text)));
      }      

And in the text changed :

private void myTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (myTextBox.Text.Length > 0)
            {
                toFilter();
            }
            myComboBox.DataContext = this; //Obviously doesn't work
        }

So I want to keep the original collection intact ( names ), and display filteredNames when something is typed in the text box. Should I bind directly the combobox to the filteredNames (initially equal to names ) and then remove by looping the names that don't match the text box input each time myTextBox_TextChanged ?

Other approach would be to change the bind of the comboBox when something is typed from names to filteredNames .

How could this be achieved the easy way?

EDIT:

Thanks for the suggestion of using CollectionViewSource, it worked perfectly in this example, but in my real program I'm having some issues. I've reduced the problematic part to this (from XAML Lover solution)

view.Filter = delegate(object o)
                        {

                            if (o.ToString().StartsWith(myTextBox.Text))
                            {
                                return true;
                            }
                            return false;

                        };

I've seen the next behaviour: If I don't write anything, once the file is loaded the comboBox is populated with data and everything is okey. If I write anything different from "Unico.Canal" all data is gone from the combobox (Unico is my namespace, and Canal is the class of the CollectionViewSource), I've realized that by trial and error. The code is bit a mess (and very long) because I have the read file method there, do you see something that could give me that error? I think I haven't put the code in the right place. Could someone explain me what exactly is doing that "delegate" and how it works?

The proper approach in WPF for filtering is to use CollectionViewSource. ICollectionView is the primary data object for any WPF Items Controls, that allow flexibility like sorting, grouping and filtering. Get the default view from your collection property.

var view = CollectionViewSource.GetDefaultView(this.names);

Set a Predicate to the Filter property, that will be executed over all the items in collection.

view.Filter = delegate(object o)
            {
                if (o.ToString().StartsWith(textbox.Text))
                {
                    return true;
                }
                return false;
            };

Set the view to the ComboBox ItemsSource,

myComboBox.ItemsSource = view;

On TextChanged event refresh the view to update the combo box.

    private void Textbox_OnTextChanged(object sender, TextChangedEventArgs e)
    {
        ((ICollectionView)myComboBox.ItemsSource).Refresh();
    }

Use a CollectionViewSource and set it's source to your names collection.

In your viewModel you will set up a collectionViewSource like so;

CollectionViewSource myCollectionViewSource = new CollectionViewSource();
myCollectionViewSource.Source = names;

You need to set up a predicate to filter the items in your collectionViewSource


myCollectionViewSource.View.Filter = new Predicate(this.MyFilter);
public bool MyFilter(string item)
{
  // put whatever filtering logic you have in here
  return item.StartsWith(myTextBox.Text);
}

Then expose your collectionViewSource as a property to the view.


public CollectionViewSource MyCollectionViewSource 
{
  get
  {
    return myCollectionViewSource;
  }
  set
  {
    myCollectionViewSource = value;
    // make sure to raise INotifyPropertyChanged here
  }
}

Then in your XAML your ComboBox will look like this;

<ComboBox ItemsSource="{Binding MyCollectionViewSource.View}" />

If i understand your problem correctly , i would change the this

public ObservableCollection<string> FilteredNames{ get; set; }

to

public ObservableCollection<string> FilteredNames
{ 
    get
       {
            if(IsNamesFilterd)
            {
                return _filteredNames;
            }
            else
            {
                return _names ;
            }
       }
 }

with changing the boolean condition in the event handler code. also NotifyPropertyChanged after changing the Boolean.

Use ICollectionView as a data-bound property type instead of ObservableCollection<string> :

namesView = CollectionViewSource.GetDefaultView(names);
namesView.Filter = item =>
{
    if (myTextBox.Text.Length > 0)
    {
        return ((string)item).StartsWith(myTextBox.Text);
    }
    return true;
};

private void myTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    namesView.Refresh();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM