title: Display and Filter XML Data in WPF Application
body: <p>I am creating a very simple WPF application that loads data from an XML file and displays it as a table.  However, I need to have the table free-text filterable (by any value, any column).  The problem is that the XML data will have <strong>an unknown number of attributes</strong>, so I can't cast the data to a known/concrete object (note that for any given XML file, the number of attributes will always be the same).  </p>
  
  <p>I originally tried the code from <strong><a href="https://stackoverflow.com/questions/17099042/filter-wpf-datagrid-values-from-a-textbox">this post</a></strong>, but found that since I can't cast my XML data to a known object, that I can't use the Filter predicate method on the DataGridCollectionView (the CanFilter method is set to false when I pass in my DataSet).  Therefore, I found that I have to use BindingListCollectionView which accepts a CustomFilter, but it's a "klunky" SQL type filter.</p>
  
  <p>Here's a random XML file that works for this purpose.</p>
  
  <pre><code>&lt;products&gt;
    &lt;product Name="Widget" Amount="123.45" Size="10 in" SomeOtherValue="foo" /&gt;
    &lt;product Name="Screw" Amount="8.52" Size="5.1 cm" SomeOtherValue="bar" /&gt;
    &lt;product Name="Bolt" Amount="2.66" Size="4 in" SomeOtherValue="con" /&gt;
    &lt;product Name="Hinge" Amount="14.00" Size="22 cm" SomeOtherValue="toso" /&gt;
  &lt;/products&gt;
  </code></pre>
  
  <p><strong>The following code works</strong> (somewhat), but it seems really dirty.  Also, it acts weird in that if I provide an invalid filter value of "xxx", no results are returned (normal), but then for "xxxx" all the results are returned, and again for "xxxxx", no results are returned.  It's the <strong>FilterString</strong> method I need help with. How can I clean up this code and eliminate my abnormal filter behavior?</p>
  
  <pre><code>public partial class MainWindow : INotifyPropertyChanged
  {
  
      private BindingListCollectionView _bindingListCollection;
      private string _filterString;
  
      public MainWindow()
      {
          InitializeComponent();
          var dataSet = new DataSet();
          dataSet.ReadXml(@"D:\index.xml");
          BindingListCollection = (BindingListCollectionView)CollectionViewSource.GetDefaultView(dataSet.Tables[0]);
          BindingListCollection.CustomFilter = null;
      }
  
      public BindingListCollectionView BindingListCollection
      {
          get { return _bindingListCollection; }
          set { _bindingListCollection = value; NotifyPropertyChanged("BindingListCollection"); }
      }
  
      public string FilterString
      {
          get { return _filterString; }
          set
          {
              _filterString = value;
              if (_bindingListCollection == null) return;
              var filterValue = string.Empty;
              foreach (DataRowView dataRowView in _bindingListCollection.SourceCollection)
              {
                  // Loop through each column in the underlying data and manually create a SQL filter
                  foreach (var columnName in dataRowView.DataView.DataViewManager.DataSet.Tables[0].Columns)
                  {
                      filterValue += string.Format("{0} LIKE '%" + _filterString + "%' OR ", columnName);
                  }
                  // I don't want to iterate through the rows, I just need to get to the raw table columns, so I break here
                  break; 
              }
  
              if (filterValue == string.Empty)
              {
                  BindingListCollection.CustomFilter = null;
              }
              else
              {
                  // add something so the last "OR" doesn't throw things off
                  BindingListCollection.CustomFilter = filterValue + " 0 = 1";
              }
  
              NotifyPropertyChanged("FilterString");
              _bindingListCollection.Refresh();
          }
      }
  
      public event PropertyChangedEventHandler PropertyChanged;
      private void NotifyPropertyChanged(string property)
      {
          if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); }
      }
  
  }
  </code></pre>
  
  <p>If you want it to help troubleshoot, here's also the presentation code</p>
  
  <pre><code>&lt;Window x:Class="MyViewer.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="368" Width="1002" Name="UI" &gt;
      &lt;StackPanel DataContext="{Binding ElementName=UI}"&gt;
          &lt;TextBox Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" /&gt;
          &lt;DataGrid ItemsSource="{Binding BindingListCollection}" /&gt;
      &lt;/StackPanel&gt;
  &lt;/Window&gt;
  </code></pre>