Project Description

Jib.Grid is a reference implementation demonstrating how to add Paging, Grouping, Freezing, and Filtering to the base Silverlight 4 DataGrid. This project can serve as the basis for your specific needs.

You need to install Microsoft Silverlight to view this content. Get Silverlight!
Get Microsoft Silverlight


This project is intended to demonstrate how each of these features can be implemented using the standard DataGrid provided in Silverlight 4. In total, the solution consists of several hundred lines of code {excluding xaml :) }. This small code-base should help others in understanding the techniques used for each feature. While fully functional, this project should serve as a reference implementation not a final solution. In several edge cases simplicity was chosen over completeness. As such, there are several limitations to this control. Before you use the control, please review these limitations outlined below. As a side note, all these limitations can be resolved, but the solutions would add too much complexity and code for this projects intended purpose.


Filtering

The filter headers are added to the DataGrid by creating a custom ColumnHeaderStyle. This implementation has a serious drawback. To make a long story short, there is no easy way of tying the filter control to the corresponding column. In the end I created a dependent property on the filter control and bound the header content of the column to this property. When the control is instantiated I iterate thought the grids column collection matching the column by its header content. Because of this solution, all columns which are to have a filter must have a unique header.


private void ColumnFilterHeader_Loaded(object sender, RoutedEventArgs e)
        {
            var header = sender as ColumnFilterControl;
            FilterHeaders.Add(header);
            if (header.HeaderContent != null)
            {
                var column = this.Columns.Where(c => c.Header != null && c.Header.ToString() == header.HeaderContent.ToString()).FirstOrDefault();
                header.Column = column;
                header.Grid = this;
                header.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(header_PropertyChanged);
                if (this.FilterType != null)
                    header.ResetFilterValues(CreateFilterColumnInfo(column));
            }

        }

Since the ItemSource for the grid is a PagedCollectionView, filtering is done by generating a predicate composed of all the filters entered by the user. This predicate is composed by each filter control dynamically creating a lamda expression and then having the grid AND each predicate into the final predicate for the PagedCollectionView's filter. For simplicity’s sake the lamda expression can only be generated off of first level properties. In addition to this, the filter controls do not support String Formatting (could not figure out how to apply it). It does support converters.


  PagedCollectionView view = this.ItemsSource as PagedCollectionView;
            Predicate<object> predicate = null;
            foreach (var filter in FilterHeaders)
                if (filter.HasPredicate)
                    if (predicate == null)
                        predicate = filter.GeneratePredicate();
                    else
                        predicate = predicate.And(filter.GeneratePredicate());
            view.Filter = predicate;

Paging

Since the grids ItemsSource is a PagedCollectionView, paging is supported with no additional code. A DataPager is added to the page with its source set to the grids ItemsSource.


    <sdk:DataPager HorizontalContentAlignment="Center" x:Name="myPager" Source="{Binding Path=ItemsSource, ElementName=grid}" PageSize="50" Grid.Row="1" />


Grouping

The grouping control is added to the grid by editting the columns ColumnHeaderStyle. Since the PagedCollectionView supports grouping, the grid only has to manage the current list of groups and grouping-state of each column.


                    case Enums.ColumnOption.AddGrouping:
                        optionCtrl.IsGrouped = true;
                        PagedCollectionView v = this.ItemsSource as PagedCollectionView;
                        PropertyGroupDescription newGroup = new PropertyGroupDescription(optionCtrl.FilterColumnInfo.PropertyPath, optionCtrl.FilterColumnInfo.Converter);
                        ColumnGroups.Add(newGroup);
                        v.GroupDescriptions.Add(newGroup);
                        break;
                    case Enums.ColumnOption.RemoveGrouping:
                        optionCtrl.IsGrouped = false;
                        PagedCollectionView view = this.ItemsSource as PagedCollectionView;
                        PropertyGroupDescription group = ColumnGroups.Where(c => c.PropertyName == optionCtrl.FilterColumnInfo.PropertyPath).FirstOrDefault();
                        if (group != null)
                        {
                            ColumnGroups.Remove(group);
                            view.GroupDescriptions.Remove(group);
                        }
                        break;


Freezing

The Silverlight DataGrid supports Freezing Columns. This is done by setting the FrozenColumnCount. When the user selects a column to freeze, the grid changes the display index to 0 and increments the FrozenColumnCount.


                    case Enums.ColumnOption.PinColumn:
                        optionCtrl.IsPinned = true;
                        int frozenCount = this.FrozenColumnCount;
                        if (optionCtrl.Column.DisplayIndex >= this.FrozenColumnCount)
                            frozenCount++;
                        optionCtrl.Column.DisplayIndex = 0;
                        this.FrozenColumnCount = frozenCount;
                        break;
                    case Enums.ColumnOption.UnpinColumn:
                        optionCtrl.IsPinned = false;
                        optionCtrl.Column.DisplayIndex = this.FrozenColumnCount - 1;
                        this.FrozenColumnCount = this.FrozenColumnCount - 1;
                        break;

Limitations

  1. The header content must be set for each column and be unique.
  2. The grid does not support property paths with a depth greater than one. ie {Binding Path=Contact.Address.City}
  3. While the grid supports converters, it does not support StringFormating

Last edited Nov 30, 2012 at 2:36 AM by Byrnesama, version 25