Trying to implement filtering

Jul 23, 2009 at 12:38 PM

I am trying to develop a filtering functionality for WPF DataGrid. I want a user to right-click any cell and select "Filter" from its context menu, and then the grid should be filtered by the cell's value.

I am trying the M-V-VM pattern. My windows's datacontext is MainWindowViewModel which has a property "Transactions". This property returns ObservableCollection, and the data grid uses this collection as its items source. So basically each row is bounded to TransactionViewModel (as you can guess, this grid lists transactions). MainWindowsViewModel has ICollectionView which is used for filtering and tracking the currently selected row. The datagrid has its property IsSynchronizedWithCurrentItem set to "true", so myCollectionView.CurrentItem gives me the currently selected TransactionViewModel.

The only thing I still need to know is by which column I need to filter. This depends on where the user clicked the context menu. So I am trying to pass this information using CommandProperty of the context menu item. And here I have a real problem. I tried this:

CommandParameter="{Binding Column.Binding.Path.Path, 
                                       RelativeSource={RelativeSource FindAncestor,
                                                       AncestorType={x:Type tk:DataGridCell}}}"
/>

This is really ugly, but this works for DataGridTextColumns. Unfortunately, I have also DataGridTemplateColumns, and they don't work (the path is different there, because I need to reach the actual cell template)...

So how can I implement this functionality? Perhaps the whole way is wrong? I didn't find any valuable example on that. The only thing I found is the WPF DataGrid autofilter implementatin on the Codeproject which doesn't work at all for some reason...

Jul 23, 2009 at 1:04 PM

I know it is almost certainly not an ideal solution, but when I was faced with this exact problem, I solved it by building the context menu dynamically (in the ContextMenuOpening event, as I recall) and using the "filter" submenu's items' Tag property to reference the column in question. I could have subclassed MenuItem and given it a separate property for this, but Tag was readily available and a column reference was all I needed. The column, in turn, was enriched with a property holding the current filter condition, and the DataGrid's Items.Filter callback simply went over the collection of columns and applied each filter condition in turn if there was one defined. At least for reasonably limited data set sizes, this worked fine - I haven't tried with really large data sets, but on the order of 2000 rows by 25 columns proved completely manageable in terms of performance - and dynamically building the context menu made it very easy to provide user feedback on filtering status both per column and for the whole grid. And if you can live with needing to subclass the DataGrid class and the few data grid column types, it's relatively painless to implement. (I was already subclassing for other reasons, so that wasn't a concern for me.)

Sep 3, 2009 at 11:55 PM

Hey guys... I'm in a similar spot you guys are in thow a bit more simple, so I was hoping you might have some insight.

I have an MVVM application using a datagrid as well... same idea as dmitryp's in that I have a GridViewModel that exposes an OC<BusinessObjectViewModel>. My business object view model has an enum property for its business state: InProcess, Finalized, and Archived.

What I am trying to do is implement 3 check boxes below the grid, that allow me to toggle the presence of items in my grid by their states. So that when "Show InProcess" is checked, they are included in the items source... when its not checked, they are removed from the items source collection... or if not removed from the OC, removed from display in the grid at least... that's probably better anyway.

Any thoughts on how I could go about doing this?

I've only seen examples of using ICV in the view's code-behind so I'm at a loss here.

 

-Trey

 

Sep 7, 2009 at 6:36 AM

I would again suggest looking at Items.Filter. It's a delegate implemented thus:

bool myGreatFilterFunction(object item)
{
    bool shouldShow = true;
    BusinessObjectType bo = item as BusinessObjectType;
    // do magic on "bo" and shouldShow //
    return shouldShow;
}

As long as you don't need to perform any lengthy operations (remember this is called once per row) this has worked well for me, and it leaves the collection untouched.

Sep 8, 2009 at 5:06 PM

Thanks for the reply. I understand the idea of using a delegate I think. If I do a better job explaining myself, here's a little bit more accurately where I am stuck:

1. I am trying to provide filter functionality in the ViewModel layer, and be one of those annoying purists who doesn't put stuff in the code-behind of the View. So the DataGrid object is inaccessible by nature in my goals.

2. My three check boxes are independent of each other, and need to remain so. Meaning, if all three are checked, all three states are shown, and any combination of those may happen.

 

Coordinator
Sep 9, 2009 at 1:23 PM

Here's an example of applying a filter in a VM:

   
    public class ViewModel : INotifyPropertyChanged
    {
        private CollectionView _dpCollection;
        private DataFilter _dataFilter;

        public CollectionView DPCollection
        {
            get
            {
                return _dpCollection;
            }
            set
            {
                _dpCollection = value;

                this.DataFilter = new DataFilter(_dpCollection);
                _dpCollection.Filter = new Predicate(this.DataFilter.FilterItem);
                OnPropertyChanged("DPCollection");
            }
        }

        public DataFilter DataFilter
        {
            get { return _dataFilter; }
            private set
            {
                _dataFilter = value;
                OnPropertyChanged("DataFilter");
            }
        }
    }

    public class DataFilter : INotifyPropertyChanged
    {
        public DataFilter(CollectionView view)
        {
            this.CollectionView = view;
        }

        public CollectionView CollectionView { get; set; }

        public bool FilterItem(object item)
        {
            bool retVal = true;
            var viewModel = item as DPViewModel;
            if (viewModel != null)
            {
                // filter here 
            }
            return retVal;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.CollectionView != null)
                this.CollectionView.Refresh();

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

 

      

 

 

 

Oct 16, 2009 at 9:03 PM

The biggest place I am stuck now with moving the filtering into the VM layer is with tying the filters to the necessary events.

For instance, checkboxes' Checked and Unchecked events. The initial handlers have to be in code-behind in a View if I understand it right.

In which case when I have an object state filters (ex. 3 check boxes functioning in parallel to toggle display). The checkboxes have event handlers for Checked and Unchecked -- adding a filter to the grid's CollectionViewSource collection on Unchecked (thus removing the items of that state), and removing the filter when Checked fires.

How could I do something like that in the ViewModel layer without coupling the two layers tightly?

-T