Archive

Archive for February, 2011

Extending OrderBy Linq extensions (Synchronous DataGrid Part 2)

A few weeks ago, I have post an article over my Synchronous DataGrid which I have created. The problem was sorting the data ‘synchronously’ of the first DataGrid with the second DataGrid. The default order mechanism of the DataGrid didn’t work correctly anymore. If you will click on a header on the first DataGrid, he sort his data but the data of the second DataGrid isn’t sorted.
The solution for this problem can be are: fire an event if the user click on a header and sort the collection by the property binded on the matching column.
But… the header hasn’t default an OnClick event… Ok, let’s implement it in the Synchronous DataGrid:
I override the OnMouseLeftButtonDown methode, I check if the mouse position on a header of the DataGrid, if yes, get the name of the property wich is binded on the column and fire a new event: DataGridHeaderClickend.

        protected override void OnMouseLeftButtonDown(
             System.Windows.Input.MouseButtonEventArgs e)
        {
            Point pt = e.GetPosition(null);
            var lst = VisualTreeHelper.FindElementsInHostCoordinates(pt, 
                                                                    this);
            var lstHeader = new List<DataGridColumnHeader>();
            foreach (var element in lst)
            {
                if (element.GetType().Equals(typeof(DataGridColumnHeader)))
                    lstHeader.Add((DataGridColumnHeader)element);
            }

            if (lstHeader.Count == 1)
            {
                foreach (var item in this.Columns)
                {
                    if (item.Header.Equals(lstHeader[0].Content))
                        OnDataGridHeaderClickend(new 
                          DataGridHeaderClickedEventArgs(lstHeader[0], 
                                                 GetBindingName(item)));
                }
            }

            base.OnMouseLeftButtonDown(e);
        }

So far so good… Now, we knows on wich column is clicked and we knows what the name of the binded property is of the column.
But… There’s is no Linq OrderBy method which have a paramater of type ‘string’.
The solution is extending the OrderBy and OrderByDescending Linq methods.
You can get the property which some reflection:

 var mem = typeof(T).GetMember(memberName).Single();

            var memType = (mem is System.Reflection.FieldInfo)
               ? ((System.Reflection.FieldInfo)mem).FieldType
               : ((System.Reflection.PropertyInfo)mem).PropertyType;

and build on this manner the regular expression for sorting your collection:

  private static Func<TSource, TKey> CreateKeySelector<TSource, TKey>
        (MemberInfo member)
        {
            Func<TSource, object> getMemberValue = c => (member is FieldInfo)
                    ? ((FieldInfo)member).GetValue(c)
                    : (member is PropertyInfo)
                        ? ((PropertyInfo)member).GetValue(c, null)
                        : null;

            if (getMemberValue == null)
                throw new NotSupportedException
                    ("Only property or field members are supported.");

            Func<TSource, TKey> ret = c => (TKey)getMemberValue(c);

            return ret;
        }

The OrderBy extention method looks like this:

  public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> source,
                                                 string memberName)
        {
            var mem = typeof(T).GetMember(memberName).Single();

            var memType = (mem is FieldInfo)
               ? ((FieldInfo)mem).FieldType
               : ((PropertyInfo)mem).PropertyType;

            var keySelector = typeof(Extensions)
                .GetMethod("CreateKeySelector", BindingFlags.Static | 
                                               BindingFlags.NonPublic)
                .MakeGenericMethod(typeof(T), memType).Invoke(null,
                                               new object[] { mem });

            var orderByMeth = typeof(Enumerable).GetMethods()
                .Where(c => c.Name == "OrderBy")
                .Where(c => c.GetParameters().Count() == 2)
                .Single()
                .MakeGenericMethod(typeof(T), memType);

            return orderByMeth.Invoke(null, 
                  new object[] { source, keySelector }) 
                              as IOrderedEnumerable<T>;
        }

NOTE: this way of working is not very performant (client-side sorting & reflection), use it only if there is not another way to sort.

You can download a sample application here.