0

I'm trying to filter a list based on what a user types into a textbox. However, nothing is happening as the user types into the box. As I've been debugging, I've placed breakpoints on the setter for this binding, but they don't trigger.

TextBox definition:

<TextBox HorizontalAlignment="Center" Text="{Binding TESTSerialNumbSearchTerm, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ToolTip="Filter Part Number" Width="180"/>

ViewModel Binding:

public String TESTSerialNumbSearchTerm
{
    get
    {
        return serialNumbSearchTerm;
    }
    set
    {
        if (serialNumbSearchTerm != value)
        {
            serialNumbSearchTerm = value;
            VisibleProfiles = FilterList(VisibleProfiles, Tuple.Create("serialNumber", value));
            OnPropertyChanged(nameof(VisibleProfiles));
            OnPropertyChanged(nameof(TESTSerialNumbSearchTerm));
        }
    }
}

Grid definition, with ItemSource:

<DataGrid MaxHeight="400" Grid.Row="0" ItemsSource="{Binding VisibleProfiles}" SelectedItem="{Binding SelectedProfile}" SelectionMode="Single" IsReadOnly="True" AutoGenerateColumns="False" VerticalScrollBarVisibility="Visible">

FilterList Method:

public List<DongleProfile> FilterList(List<DongleProfile> inputList, Tuple<string, string> filter)
{
    List<DongleProfile> newList = new List<DongleProfile>();
    foreach (DongleProfile item in inputList)
    {
        switch (filter.Item1)
        {
            case "serialNumber":
                if (item.SerialNumberPrefix.Contains(filter.Item2))
                {
                    newList.Add(item);
                }
                break;
                // Similar cases
        }
    }
    return newList;
}
Harry Adams
  • 423
  • 5
  • 16
  • Please, add `FilterList` method and list binding/xaml code – Pavel Anikhouski Oct 03 '19 at 08:49
  • How is `VisibleProfiles` defined? Try to make it `ObservableCollection` – Pavel Anikhouski Oct 03 '19 at 08:58
  • Was a List, have changed it to ObservableCollection but no change – Harry Adams Oct 03 '19 at 09:16
  • Look at `CollectionViewSource` class in this [thread](https://stackoverflow.com/questions/20888619/proper-way-to-use-collectionviewsource-in-viewmodel) – Pavel Anikhouski Oct 03 '19 at 09:19
  • The items in VisibleProfiles are appearing in the grid, as a `List` or `ObservableCollection`. I've put a breakpoint on the setter for the TextBox value and that's never breaking, as if nothing is being typed – Harry Adams Oct 03 '19 at 09:20
  • @HarryAdams: Where is the `TextBlock` located in your view and what is its `DataContext` at runtime? Is `TESTSerialNumbSearchTerm` and `VisibleProfiles` defined in the same class? – mm8 Oct 03 '19 at 13:13
  • @mm8 TextBlock is within a `DataGridTemplateColumn.HeaderTemplate`, both bindings you mentioned are in the same class (the ViewModel) – Harry Adams Oct 03 '19 at 13:47
  • @mm8 Not sure on the DataContext however - how can I check it? I've started looking at `Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}...` but I'm not sure if this is the right line of thinking? – Harry Adams Oct 03 '19 at 13:50
  • @HarryAdams: Please see my answer. – mm8 Oct 03 '19 at 13:51

2 Answers2

1

Try the following idea:

Public field for filtering textbox

public string md_FilterString
        {
            get { return _FilterString; }
            set
            {
                if (_FilterString != value)
                {
                    _FilterString = value;
                    mf_MakeView();
                    OnPropertyChanged("md_FilterString");
                }
            }
        }

Public field for datagrid binding:

public ICollectionView md_LogEntriesStoreView { get; private set; }

XAML:

..
<TextBox Grid.Column="1"
                     Text="{Binding md_FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                     Height="22"
                     HorizontalAlignment="Stretch"
                     Margin="0,0,0,0"
                     Name="textBoxFilter"
                     VerticalAlignment="Center"/>
..
<DataGrid ItemsSource="{Binding md_LogEntriesStoreView, UpdateSourceTrigger=PropertyChanged}"
..
</DataGrid>

mf_MakeView func configures the composition of the collection md_LogEntriesStoreView:

 private void mf_MakeView()
        {
            if (d_Items== null) return;
            md_LogEntriesStoreView = CollectionViewSource.GetDefaultView(d_Items);
            md_LogEntriesStoreView.Filter = mf_UserFilter;
            OnPropertyChanged("md_LogEntriesStoreView");
        }

Where d_Items - are directly the elements of your observable collection that will be displayed in the control datagrid

The filtering function (mf_UserFilter) is presented in a general way for an object containing text fields. You can replace it for optimization purposes with your own version, adapted to your goals:

private bool mf_UserFilter(object item)
        {
            string s = md_FilterString;
            if (String.IsNullOrWhiteSpace(s))
                return true;
            else
            {
                var srcT = item.GetType();
                foreach (var f in srcT.GetFields())
                {
                    string str = f.GetValue(item) as string;
                    if (String.IsNullOrWhiteSpace(str)) continue;
                    bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
                    if (b) return true;
                }
                foreach (var f in srcT.GetProperties())
                {
                    string str = f.GetValue(item, null) as string;
                    if (String.IsNullOrWhiteSpace(str)) continue;
                    bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
                    if (b) return true;
                }

                return false;
            }
        }

UPDATE: Full text: Code part:

namespace WpfApplication2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindow_ModelView();
        }
    }

    public class MainWindow_ModelView : NotifyBase
    {
        private string _FilterString = String.Empty;

        public ObservableCollection<ItemClass> d_Items { get; set; }
        public ICollectionView md_LogEntriesStoreView { get; private set; }

        public string md_FilterString
        {
            get { return _FilterString; }
            set
            {
                if (_FilterString != value)
                {
                    _FilterString = value;
                    mf_MakeView();
                    OnPropertyChanged("md_FilterString");
                }
            }
        }

        public MainWindow_ModelView()
        {
            d_Items = new ObservableCollection<ItemClass>() { new ItemClass() { d_Text1 = "Item1Text1", d_Text2 = "Item1Text2" }, 
                new ItemClass() { d_Text1 = "Item2Text1", d_Text2 = "Item2Text2" }, 
                new ItemClass() { d_Text1 = "Item3Text1", d_Text2 = "Item3Text2" } };

            md_LogEntriesStoreView = CollectionViewSource.GetDefaultView(d_Items);
        }

        private void mf_MakeView()
        {
            if (d_Items == null) return;
            md_LogEntriesStoreView = CollectionViewSource.GetDefaultView(d_Items);
            md_LogEntriesStoreView.Filter = mf_UserFilter;
            OnPropertyChanged("md_LogEntriesStoreView");
        }
        private bool mf_UserFilter(object item)
        {
            string s = _FilterString;
            if (String.IsNullOrWhiteSpace(s))
                return true;
            else
            {
                var srcT = item.GetType();
                foreach (var f in srcT.GetFields())
                {
                    string str = f.GetValue(item) as string;
                    if (String.IsNullOrWhiteSpace(str)) continue;
                    bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
                    if (b) return true;
                }
                foreach (var f in srcT.GetProperties())
                {
                    string str = f.GetValue(item, null) as string;
                    if (String.IsNullOrWhiteSpace(str)) continue;
                    bool b = str.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0;
                    if (b) return true;
                }

                return false;
            }
        }
    }

    public class ItemClass : NotifyBase
    {
        public string d_Text1 { get; set; }
        public string d_Text2 { get; set; }
    }

    public class NotifyBase : INotifyPropertyChanged
    {
        Guid id = Guid.NewGuid();

        [Browsable(false)]
        [System.Xml.Serialization.XmlAttribute("ID")]
        public Guid ID
        {
            get { return id; }
            set
            {
                if (id != value)
                {
                    id = value;
                    OnPropertyChanged("ID");
                }
            }
        }

        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
    }

XAML part:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox Height="23"
                 Text="{Binding md_FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                 HorizontalAlignment="Stretch"
                 Name="textBox1"
                 Margin="2"
                 VerticalAlignment="Top"/>
        <DataGrid ItemsSource="{Binding md_LogEntriesStoreView}"
                  AutoGenerateColumns="False"
                  Grid.Row="1"
                  Margin="2"
                  HorizontalAlignment="Stretch"
                  Name="dataGrid1"
                  VerticalAlignment="Stretch">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Path = d_Text1}"
                                Width="Auto"
                                IsReadOnly="True"/>
                <DataGridTextColumn Binding="{Binding Path = d_Text2}"
                                    Width="*"
                                    IsReadOnly="True" />
             </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Result:

Res1 res2

1

If the TextBox is located in a DataGrid, you could use a RelativeSource to bind to a property of the view model:

<TextBox HorizontalAlignment="Center" 
         Text="{Binding DataContext.TESTSerialNumbSearchTerm, UpdateSourceTrigger=PropertyChanged, 
            RelativeSource={RelativeSource AncestorType=DataGrid}}" 
         ToolTip="Filter Part Number" Width="180"/>
mm8
  • 163,881
  • 10
  • 57
  • 88