Persisting a custom filter

8 posts, 1 answers
  1. Alex
    Alex avatar
    23 posts
    Member since:
    Oct 2012

    Posted 14 Nov 2012 Link to this post

    In my grid I have a custom collection filter that I created using this blog post: http://blogs.telerik.com/xamlteam/posts/11-12-05/filtering-collection-properties-with-radgridview-for-silverlight-and-wpf.aspx .

    I'm currently adding persistence to my grid and running into issues saving/restoring my custom filter. It seems that I need to implement IColumnFilterDescriptor to properly reflect UI changes when I set my filter programmatically upon restoring a persisted grid. However (as in the above demo) my custom filter descriptor only implements IFilterDescriptor. Could you provide some information on how I can implement my own filter descriptor and also be able to persist it?

    Thanks.
  2. Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 15 Nov 2012 Link to this post

    Hello,

    What you can try to do is to create your custom column filter descriptor that inherist from our stock one.

    Then you need to override the CreateFilterExpression and return an expression that will be used by our data engine for filtering. You will build this custom expression based on what the user has entered in the UI, i.e. you will be reading stuff from:

    ((IColumnFilterDescriptor)this).DistinctFilter and ((IColumnFilterDescriptor)this).FieldFilter.

    Take a careful look at the ICoumnFilterDescriptor interface - it is a direct representation of the filtering UI. More about this you can learn from here. Please read this before going on.

    Once you have gathered the data you only need to build your LINQ expression and return it to our data engine.

    I have attached a sample project that builds a custom column filter descriptor for a flags enum type, but the idea and the approach should be the same in your case. You can debug this project to see what is going on and then tailor the custom column filter descriptor to your particular needs, i.e. return a LINQ Expression that will filter your collection property.

    I hope this helps.

    Regards,
    Rossen Hristov
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

  3. UI for WPF is Visual Studio 2017 Ready
  4. Alex
    Alex avatar
    23 posts
    Member since:
    Oct 2012

    Posted 15 Nov 2012 Link to this post

    I've made my filter inherit from a stock one. This is what it looks like: 

    public sealed class CollectionFilterDescriptor : MemberColumnFilterDescriptor
    {
        private static readonly MethodInfo EnumerableCastMethod = typeof(Enumerable).GetMethod("Cast");
        private static readonly MethodInfo GenericContainsMethod = GetGenericContainsMethodInfo();
     
        private static MethodInfo GetGenericContainsMethodInfo()
        {
            var methodCall = ((MethodCallExpression)((Expression<Func<IEnumerable<object>, bool>>)
                (source => source.Contains(null))).Body).Method.GetGenericMethodDefinition();
            return methodCall.MakeGenericMethod(typeof(object));
        }
     
        private readonly GridViewBoundColumnBase column;
     
        public CollectionFilterDescriptor(GridViewBoundColumnBase column)
            : base(column)
        {
            this.column = column;
        }
     
        public override Expression CreateFilterExpression(Expression instance)
        {
            var propertyName = this.column.DataMemberBinding.Path.Path;
            var collectionPropertyAccessor = Expression.Property(instance, propertyName);
            var genericCollectionPropertyAccessor = Expression.Call(
                null, EnumerableCastMethod.MakeGenericMethod(new[] { typeof(object) }), collectionPropertyAccessor);
            var value = ((IColumnFilterDescriptor)this).DistinctFilter.DistinctValues.First();
            Expression result = Expression.Call(
                GenericContainsMethod, genericCollectionPropertyAccessor, Expression.Constant(value));
     
            return result;
        }

    I'm using this filter to filter a collection of strings based on the string typed into my little filter.

    I try to restore this filter using code as such:
    private static void RestoreFilterDescriptors(IEnumerable<FilterSetting> filterSettings, RadGridView gridView)
            {
                gridView.FilterDescriptors.SuspendNotifications();
                foreach (var c in gridView.Columns)
                {
                    if (c.ColumnFilterDescriptor.IsActive) c.ClearFilters();
                }
     
                foreach (var setting in filterSettings)
                {
                    var column = gridView.Columns[setting.ColumnUniqueName];
                    var columnFilter = column.ColumnFilterDescriptor;
     
                    foreach (var distinctValue in setting.SelectedDistinctValues)
                    {
                        columnFilter.DistinctFilter.AddDistinctValue(distinctValue);
                    }
     
                    if (setting.Filter1 != null)
                    {
                        columnFilter.FieldFilter.Filter1.Operator = setting.Filter1.Operator;
                        columnFilter.FieldFilter.Filter1.Value = setting.Filter1.Value;
                        columnFilter.FieldFilter.Filter1.IsCaseSensitive = setting.Filter1.IsCaseSensitive;
                    }
     
                    columnFilter.FieldFilter.LogicalOperator = setting.FieldFilterLogicalOperator;
     
                    if (setting.Filter2 != null)
                    {
                        columnFilter.FieldFilter.Filter2.Operator = setting.Filter2.Operator;
                        columnFilter.FieldFilter.Filter2.Value = setting.Filter2.Value;
                        columnFilter.FieldFilter.Filter2.IsCaseSensitive = setting.Filter2.IsCaseSensitive;
                    }
                }
     
                gridView.FilterDescriptors.ResumeNotifications();
            }


    Unfortunately, I get one of two errors depending on when I try to do this.

    Error 1 

    Error: The value "Telerik.Windows.Controls.GridView.MemberColumnFilterDescriptor" is not of type
    Stack Trace:
    at System.ThrowHelper.ThrowWrongValueTypeArgumentException(Object value, Type targetType)
       at System.Collections.ObjectModel.Collection`1.System.Collections.IList.Add(Object value)
       at Telerik.Windows.Data.CollectionHelper.Equalize(IList left, IList right, IEqualityComparer comparer)
       at Telerik.Windows.Data.CollectionHelper.Equalize(IList left, IList right)
       at Telerik.Windows.Data.DataItemCollection.EqualizeFilterDescriptors()
       at Telerik.Windows.Data.DataItemCollection.EqualizeDescriptors()
       at Telerik.Windows.Data.DataItemCollection.set_CollectionView(QueryableCollectionView value)
       at Telerik.Windows.Data.DataItemCollection.CreateAndSetCollectionView(IEnumerable source, Type itemType)
       at Telerik.Windows.Data.DataItemCollection.SetItemsSource(IEnumerable source, Type itemType)
       at Telerik.Windows.Controls.GridView.GridViewDataControl.<>c__DisplayClass14.<Bind>b__13()
       at Telerik.Windows.Controls.CursorManager.PerformTimeConsumingOperation(FrameworkElement frameworkElement, Action action)
       at Telerik.Windows.Controls.GridView.GridViewDataControl.Bind(Object newValue)
       at Telerik.Windows.Controls.GridView.GridViewDataControl.OnApplyTemplate()
       at System.Windows.FrameworkElement.ApplyTemplate()
       at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       at System.Windows.UIElement.Measure(Size availableSize)
       at System.Windows.Controls.DockPanel.MeasureOverride(Size constraint)
       at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       at System.Windows.UIElement.Measure(Size availableSize)
       at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
       at System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)
       at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       at System.Windows.UIElement.Measure(Size availableSize)
       at System.Windows.Documents.AdornerDecorator.MeasureOverride(Size constraint)
       at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       at System.Windows.UIElement.Measure(Size availableSize)
       at System.Windows.Controls.Border.MeasureOverride(Size constraint)
       at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       at System.Windows.UIElement.Measure(Size availableSize)
       at System.Windows.Window.MeasureOverrideHelper(Size constraint)
       at System.Windows.Window.MeasureOverride(Size availableSize)
       at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       at System.Windows.UIElement.Measure(Size availableSize)
       at System.Windows.Interop.HwndSource.SetLayoutSize()
       at System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)
       at System.Windows.Interop.HwndSource.set_RootVisual(Visual value)
       at System.Windows.Window.SetRootVisual()
       at System.Windows.Window.SetRootVisualAndUpdateSTC()
       at System.Windows.Window.SetupInitialState(Double requestedTop, Double requestedLeft, Double requestedWidth, Double requestedHeight)
       at System.Windows.Window.CreateSourceWindow(Boolean duringShow)
       at System.Windows.Window.CreateSourceWindowDuringShow()
       at System.Windows.Window.SafeCreateWindowDuringShow()
       at System.Windows.Window.ShowHelper(Object booleanBox)
       at System.Windows.Window.Show()

    Error 2
    Error:    Invalid cast from 'System.String' to 'System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.
    Stack Trace: 
    at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
       at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider)
       at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
       at Telerik.Windows.Data.Expressions.OperatorValueFilterDescriptorExpressionBuilderBase.CreateValueExpression(Type targetType, Object value, CultureInfo culture)
       at Telerik.Windows.Data.Expressions.OperatorValueFilterDescriptorExpressionBuilderBase.CreateBodyExpression()
       at Telerik.Windows.Data.FilterDescriptor.CreateFilterExpression(ParameterExpression parameterExpression)
       at Telerik.Windows.Data.FilterDescriptorBase.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Data.Expressions.FilterDescriptorCollectionExpressionBuilder.CreateBodyExpression()
       at Telerik.Windows.Data.CompositeFilterDescriptor.CreateFilterExpression(ParameterExpression parameterExpression)
       at Telerik.Windows.Data.FilterDescriptorBase.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Controls.GridView.DistinctValuesFilterDescriptor.Telerik.Windows.Data.IFilterDescriptor.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Data.Expressions.FilterDescriptorCollectionExpressionBuilder.CreateBodyExpression()
       at Telerik.Windows.Data.CompositeFilterDescriptor.CreateFilterExpression(ParameterExpression parameterExpression)
       at Telerik.Windows.Data.FilterDescriptorBase.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Controls.GridView.ColumnFilterDescriptor.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Data.Expressions.FilterDescriptorCollectionExpressionBuilder.CreateBodyExpression()
       at Telerik.Windows.Data.CompositeFilterDescriptor.CreateFilterExpression(ParameterExpression parameterExpression)
       at Telerik.Windows.Data.FilterDescriptorBase.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Data.CompositeFilterDescriptorCollection.CreateFilterExpression(Expression instance)
       at Telerik.Windows.Data.ICompositeFilterDescriptorExtensions.GetFilterFunction(ICompositeFilterDescriptor filterDescriptors, Type itemType)
       at Telerik.Windows.Data.QueryableCollectionView.InitializeInternalList(IQueryable view)
       at Telerik.Windows.Data.QueryableCollectionView.CreateInternalList()
       at Telerik.Windows.Data.QueryableCollectionView.EnsureInternalList()
       at Telerik.Windows.Data.QueryableCollectionView.get_InternalList()
       at Telerik.Windows.Data.QueryableCollectionView.InternalIndexOf(Object item)
       at Telerik.Windows.Data.QueryableCollectionView.IndexOf(Object item)
       at Telerik.Windows.Data.QueryableCollectionView.AdjustCollectionChangedIndex(Object item, Int32 index)
       at Telerik.Windows.Data.QueryableCollectionView.AdjustOldIndices(NotifyCollectionChangedEventArgs args)
       at Telerik.Windows.Data.QueryableCollectionView.ProcessSynchronousCollectionChanged(NotifyCollectionChangedEventArgs args)
       at Telerik.Windows.Data.QueryableCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
       at Telerik.Windows.Data.QueryableCollectionView.OnSourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
       at Telerik.Windows.Data.QueryableCollectionView.Telerik.Windows.Data.IWeakEventListener<System.Collections.Specialized.NotifyCollectionChangedEventArgs>.ReceiveWeakEvent(Object sender, NotifyCollectionChangedEventArgs args)
       at Telerik.Windows.Data.WeakEvent.WeakListener`1.Handler(Object sender, TArgs args)
       at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
       at System.Collections.ObjectModel.ObservableCollection`1.InsertItem(Int32 index, T item)
       at System.Collections.ObjectModel.Collection`1.Insert(Int32 index, T item)

    What am I missing here?

    Thanks for you help!



  5. Alex
    Alex avatar
    23 posts
    Member since:
    Oct 2012

    Posted 15 Nov 2012 Link to this post

    Oops, looks like the first error didn't paste correctly. It reads: 

    The value "Telerik.Windows.Controls.GridView.MemberColumnFilterDescriptor" is not of type "Telerik.Windows.Data.IFilterDescriptor" and cannot be used in this generic collection.
    Parameter name: value
  6. Answer
    Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 15 Nov 2012 Link to this post

    Hello,

    I am not sure whether this complex approach would work after all with all that customization. And it seems like a big overhead regarding to what you are trying to achieve in the end.

    I have an idea. A much simpler approach expoiting the fact that the string class supports the Contains operator out-of-the-box.

    Can't you add a new readonly string property on your business object? This property will concatenate all strings from the List property with or without commans and return them. Then while your column is bound to the original List<string> property, you will set its FilterMemberPath to be this new readonly property.

    Then you can hide the distinct values portion of the filtering control by setting column.ShowDistinctFilters=false. You will also remove all other operators execept for Contains and DoesNotContain by attaching to the FilterOperatorsLoading event of the grid and checking whether it is called for this particular column.

    In this way, what your user will see is the two field filters (the bottom part of the control). When he types a word and selectes Contains or DoesNotContain -- this word will be searched against your new readonly property that concatenates all strings from the list.

    This will be a much cleaner and easier approach, since the one with building lambda expressions for the List.Contains methods is quite error prone. I myself have to debug those LINQ expressions a hundred times until I get them right.

    Is this simpler approach appropriate for your case?

    All the best,
    Rossen Hristov
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

  7. Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 15 Nov 2012 Link to this post

    Hi,

    Oh and I forgot to mention -- with this simpler approach you will get the save/restore stuff out-of-the-box since this will be just a plain ordinary string column filter -- just like all the rest of them.

    All the best,
    Rossen Hristov
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

  8. Alex
    Alex avatar
    23 posts
    Member since:
    Oct 2012

    Posted 15 Nov 2012 Link to this post

    Yes, I think this approach should work fine. Is there a way to hide one of the field filters? I only need one of them.

    Thanks a lot!
  9. Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 19 Nov 2012 Link to this post

    Hello,

    The only way to hide only one of them would be delete it from the XAML of the FilteringControl template.

    Or you can locate what you want to hide with ChildrenOfType and set its Visibility to collapsed. 
     
    Greetings,
    Rossen Hristov
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

Back to Top
UI for WPF is Visual Studio 2017 Ready