Tuesday, July 9, 2013

Advanced Usage of Grouping in CollectionView with ItemsPresenter

CollectionView is an object that we generally use while dealing with a collection in WPF. It is an object structure that supports a collection as well some of the inherent features which a user might always need while dealing with a collection. Some of the features like Sorting, Grouping, Filtering are automatically implemented in a CollectionView.  I have written one article on how to deal with CollectionView way back in Aug 2010 which explains almost everything you need to do while dealing with collection in WPF. But it does not give the entire story.

Controls that can show a Collection in WPF is somehow derived from ItemsPresenter. The ItemsPresenter has a property called ItemsSource which takes an object of ICollectionView, and hence it is one of the important interfaces considered so far.

There are some advanced scenarios where the general Grouping or sorting does not makes sense. Here in this article I am going to deal with such advanced scenarios which might be worth mentioning.



Advanced Grouping with CollectionView

Groping in ICollectionView is done by adding GroupDescription. Let us consider the case with some code:

ListCollectionView collectionView = new ListCollectionView(this.MyCollection);
collectionView.GroupDescriptions.Add(new PropertyGroupDescription("GroupProperty"));
return collectionView;

The ListCollectionView is an implementation of ICollectionView which allows you specify a GroupDescription on a Collection. Here in the above code, the MyCollection is an ObservableCollection that adds a GroupDescription on GroupProperty.

public class GroupedObjects
{
   // Other properties
    public string GroupProperty { get; set; }
}

Here the GroupedObject represents each object in the collection. The GroupProperty is of type string and hence when two object has same GroupProperty value, it will be treated on the same group in the collection.

this.MyCollection.Add(new GroupedObjects { GroupProperty = "A" });
this.MyCollection.Add(new GroupedObjects { GroupProperty = "A" });

For instance, the first two objects belong to the same group.

Now when showing the Group on the ItemsPresenter, you need to show the GroupProperty in the header by defining the GroupStyle.
<ListView.GroupStyle>
    <GroupStyle>
        <GroupStyle.HeaderTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
</ListView.GroupStyle>

Here the Name property is automatically assigned to the GroupProperty value for each Group while the elements are getting added. Thus while defining GroupStyle, if you specify Binding on Name, it will show the Group header.

Now think of a situation, where you need multiple data for a Group. The Name supports objects and hence you can specify a whole object for Name. Lets change our class a little bit.

public class MyCustomGroup
{
      public string Type { get; set; }
      public string DisplayText {get; set;}
}
public class GroupedObjects
{
     public string Item {get; set;}
     public MyCustomGroup {get;set;}
}

Here the MyCustomGroup defines the group of GroupedObjects. Hence if you specify MyCustomGroup in properties, it will assign the whole MyCustomGroup to the Name property of the Style. You can easily define the GroupStyle like this :

<ListView.GroupStyle>
    <GroupStyle>
        <GroupStyle.HeaderTemplate>
            <DataTemplate>
<StackPanel Orientation="Horizonta">
                     <TextBlock Text="{Binding Name.Type}" />
                     <TextBlock Text="{Binding Name.DisplayText" />
               </StackPanel>
            </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
</ListView.GroupStyle>

Here the Name holds the entire object and the properties can be evaluated. But how does it then determine the equality of the Groups ? Well, the PropertyGroupDescription calls the Equals method on Group each time the item is evaluated. So if you override equals and return boolean based on the actual group, it can easily determine the group.

public class MyCustomGroup
{
      public string Type { get; set; }
      public string DisplayText {get; set;}
      public override bool Equals(object obj)
      {
         var groupdata = obj as MyCustomGroup;
        return this.Type.Equals(groupData.Type);
      }
}
Here the Equals is called to determine the equality of two MyCustomGroup objects. Implementing this will do the job.

Moving the Group Equality to another method than Equals

Sometimes, in advanced scenarios, you might also wonder how to deal the equality of Group in a separate method when the actual Equals method is used somewhere else in your framework. You could do that by inheriting the PropertyGroupDescription class and create an own implementation.

public class MyCustomGroup
{
      public string Type { get; set; }
      public string DisplayText {get; set;}
     public override bool Equals(object obj)
      {
         var groupdata = obj as MyCustomGroup;
        return this.DisplayText.Equals(groupData.DisplayText);
      }
      public bool GroupEquals(object obj)
      {
         var groupdata = obj as MyCustomGroup;
        return this.Type.Equals(groupData.Type);
      }
}
Consider the class MyCustomGroup, it already have an Equals operator implemented which equates DisplayText. There is a separate method called GroupEquals which needed to be used while equating groups. Here is what you need to do :

   public class MyGroupDescription : PropertyGroupDescription
    {
        public MyGroupDescription(string groupName) : base(groupName) { }
        public override bool NamesMatch(object thisgroup, object othergroup)
        {
            var groupdata = thisgroup as MyCustomGroup;
            return groupdata.GroupEquals(othergroup);
        }
    }
Here we have derived the PropertyGroupDescription and overridden the NamesMatch which in turn calls the Equals operator on the object. We rather redirected the same to GroupEquals instead.  You can even inherit from abstract type GroupDescription while in that case, you will have responsibility to override GroupNameForItem as well. The GroupNameForItem has responsibility to determine the Group object for an item.

Conclusion


Generally, Grouping an ICollectionView is rather very easy to implement, until when I struck into a situation where I needed to implement grouping all myself. I tried to search over internet and found no good link which explains the entire story. I hope in case of need for advanced grouping, you can still make use of ICollectionView in WPF rather than moving in wrong direction. I hope you will find this post handy.

Thank you for reading.

Happy programming.

1 comment:

  1. As such, it mustn't be too difficult to spend
    some time constructing a database of tables like what is suggested at where they have got provided a sample database spec for usage in a hotel room
    reservation application. s no secret that illegal file sharing remains to
    be rampant in several countries, such as the US, however, it might not be government interference that eventually leads towards the end of
    the age of illegal downloads. Typically, the My Documents folder may be the best one to use.


    My webpage: pirate proxy ()

    ReplyDelete

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.

Author's new book

Abhishek authored one of the best selling book of .NET. It covers ASP.NET, WPF, Windows 8, Threading, Memory Management, Internals, Visual Studio, HTML5, JQuery and many more...
Grab it now !!!