How does filters check for equality?

3 posts, 1 answers
  1. haagel
    haagel avatar
    52 posts
    Member since:
    Feb 2010

    Posted 31 May 2011 Link to this post

    I'm wondering how the filters in RadGridView work. How do they check for equality?

     

    After some experimentation it seems that they check for "referential equality" (is that what is it called??), that is the filter checks if the rows have exactly the same object as the filter. So even if a column shows a custom type of mine and I have implemented the Equals() method, it won't matter.

     

    Lets say I have a custom type representing a customer:

    public class Customer : IComparable
    {
        public int Id { get; set; }
        public string Name { get; set; }
     
        public Customer(int id, string name)
        {
            Id = id;
            Name = name;
        }
     
        public override string ToString()
        {
            return Name;
        }
     
        public virtual int CompareTo(object obj)
        {
            Customer otherCustomer = obj as Customer;
            if (otherCustomer != null)
            {
                return this.Id.CompareTo(otherCustomer.Id);
            }
            else
            {
                throw new ArgumentException("Can only compare to other Customer instances.");
            }
        }
     
        public override bool Equals(object obj)
        {
            if (obj.GetType() == typeof(Customer))
            {
                Customer otherCustomer = (Customer) obj;
                return otherCustomer.Name == Name;
            }
            else
            {
                return false;
            }
        }
     
    }


    I'm using MVVM and each row displayed in the gridview has a viewmodel. This viewmodel has the following code:

    private Customer _contact;
    public Customer Contact
    {
        get
        {
            return _contact;
        }
        set
        {
            if (value != _contact)
            {
                _contact = value;
                OnPropertyChanged("Contact");
            }
        }
    }
     
    public BindingList<Customer> AllContacts { get; private set;}

    The row-viewmodel has a property called "Contact" that is of my custom type. It also has a BindingList of the same type that will contain all available contact/customers.

    And in my XAML I have this code:

    <tgv:RadGridView ItemsSource="{Binding Path=MyData, Mode=OneWay}" >
        <tgv:RadGridView.Columns>
             
            <!-- Columns excluded for brevity... -->
             
            <tgv:GridViewDataColumn Name="ContactColumn"
                                  Header="Contact"
                                  DataMemberBinding="{Binding Path=Contact}">
                <tgv:GridViewDataColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Path=Contact.Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
                    </DataTemplate>
                </tgv:GridViewDataColumn.CellTemplate>
                <tgv:GridViewDataColumn.CellEditTemplate>
                    <DataTemplate>
                        <ti:RadComboBox ItemsSource="{Binding Path=AllContacts, Mode=OneTime}"
                                      DisplayMemberPath="Name"
                                      SelectedItem="{Binding Path=Contact, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                    </DataTemplate>
                </tgv:GridViewDataColumn.CellEditTemplate>
            </tgv:GridViewDataColumn>
        </tgv:RadGridView.Columns>
    </tgv:RadGridView>

    As you can see the column that shows the contact binds to the property Contact on the row-viewmodel. In the cell edit template there's a ComboBox that allows the user to choose one contact from the list with all contacts.

     

    "MyData" that the gridview is bound to is a QueryableCollectionView that has all the rows (row-viewmodels) that are to be displayed. It is created something like this:

    RadObservableCollection<DataRowViewModel> myDataRows = new RadObservableCollection<DataRowViewModel>();
    //...Here would be some code to fill myDataRows with row-viewmodels...
    MyData = new QueryableCollectionView(myDataRows);

     

    All this works fine.

     

    Now I want to apply filtering on that column by code. Let's say that there are ten available customers. If I open the filter popup on the gridview all ten are displayed in the list of checkboxes where you can specify which ones to show. These are the "distict filters". To apply distinct filters in code behind I would do something like this:

    MyData.FilterDescriptors.Clear();    //Clear old filters
    ColumnFilterDescriptor cfd = new ColumnFilterDescriptor(ContactColumn);
    FilterDescriptor fd = new FilterDescriptor();
    fd.Member = "Contact";
    fd.Operator = FilterOperator.IsEqualTo;
    fd.Value = new Customer(1, "David");
    fd.IsCaseSensitive = true;
    cfd.DistinctFilter.FilterDescriptors.Add(fd);
    MyData.FilterDescriptors.Add(cfd);

    When I run this I see that a filter is applied, but all my rows are removed by the filter, even those that has Id=1 and Name=David.

     

    I have tried a different model where I don't create a new Customer and put in the Value of the filter but instead put an instance of Customer that is actually in the list AllCustomers. When I do this it works fine. The filter removes all rows except those who has that specific customer (David) as customer. This leads me to think that the filter checks equality by checking "referential equality" and does not use the Equals method.

     

    Am I right in this assumption or is it something else I'm missing?

  2. Answer
    Rossen Hristov
    Admin
    Rossen Hristov avatar
    2478 posts

    Posted 31 May 2011 Link to this post

    Hi haagel,

    You are correct.

    The filtering is performed through a LINQ Equality Expression which will compare the respective FilterDescriptor.Member and FilterDescriptor.Value with a "==" (that is in case the FilterOperator is IsEqualTo)

    The only possible way to enable filtering on a complex type would be to implement the IComparableand IEquatable interfaces. Additionally, the class should have its Equals and GetHashCode methods overriden. You must have GetHashCode, since it appears that the Distinct LINQ Extension method uses it: The class should also override the "==" and "!=" operators.

    If all of these conditions are met, then the data engine will be able to compare items of this type and thus perform filtering on a list of such objects.

    The easier approach would be to expose a new read-only integer property, let's say ContactID, which will return only the ID of the respective Customer and not the whole Customer. In this way you will be working with integers and you will no longer need to do all of the above. That is, if the ID is uniquely representing its Customer, which I am almost sure it is.

    I hope this helps.

    Best wishes,
    Ross
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  3. UI for WPF is Visual Studio 2017 Ready
  4. haagel
    haagel avatar
    52 posts
    Member since:
    Feb 2010

    Posted 01 Jun 2011 Link to this post

    Thank you Ross! :)

    That was just the information I needed.
Back to Top