Tuesday, March 25, 2014

Sorting Evolution (7) - Sorting a WPF ListView/GridView by clicking on the header - Attached Property


Seventh generation

With the last post (Sixth Generation) we have improved our sorting mechanism that it can be easily reused in diverse projects. From that point no further changes are necessary. Nevertheless in this post we will use the developed sorting mechanism combined with Attached Property. This will remove the possibility to use the sorting mechanism within the ViewModel although sorting can be placed within XAML, only. Therefore we have to do some changes.

The Sorting class is refactored so that there is only one public method (Sort), the method SetAdorner is called from that. Determination of SortDirection is done only once in SetAdorner and is removed from Sort. The property SetData<T> and method GetView are removed. Sorting is initialized now from Attached Property (View side) not by ViewModel.

public void Sort(object columnHeader, CollectionView list)
{
    string column = SetAdorner(columnHeader);
 
    list.SortDescriptions.Clear();
    list.SortDescriptions.Add(
        new SortDescription(column, _sortDirection));
}

To create a custom Attached Property a static DependencyProperty is defined (IsSortingProperty) by using the RegisterAttached method. This method has the possibility to set a PropertyMetadata as parameter in that a PropertyChangedCallback can be defined (OnSortingPropertyChanged) by using Delegates. In the callback method an event handler is added to/removed from ListView that is called on clicking on a column header. The event handler ensures that the column is sorted. In order that the Attached Property can be used with several ListView a Dictionary is used to store a Sorting object to each ListView object. Furthermore a static Get Accessor (GetIsSorting) and a static Set Accessor (SetIsSorting) is added.

private static IDictionary<ListViewSorting> _sorting;
static SortingAttached()
{
    _sorting = new Dictionary<ListViewSorting>();
}
 
public static readonly DependencyProperty IsSortingProperty =
    DependencyProperty.RegisterAttached(
    "IsSorting",
    typeof(Boolean),
    typeof(SortingAttached),
    new PropertyMetadata(false, OnSortingPropertyChanged));
 
public static bool GetIsSorting(ListView element)
{
    return (bool)element.GetValue(IsSortingProperty);
}
 
public static void SetIsSorting(ListView element, Boolean value)
{
    element.SetValue(IsSortingProperty, value);
}
 
private static void OnSortingPropertyChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    ListView listView = d as ListView;
 
    if (listView != null)
    {
        if (e.NewValue is bool)
        {
            if ((bool)e.NewValue)
            {
                listView.AddHandler(GridViewColumnHeader.ClickEvent,
                    new RoutedEventHandler(OnColumnHeaderClicked));
                _sorting.Add(listView, new Sorting());
            }
            else
            {
                listView.RemoveHandler(GridViewColumnHeader.ClickEvent,
                    new RoutedEventHandler(OnColumnHeaderClicked));
                _sorting.Remove(listView);
            }
        }
    }
}
 
private static void OnColumnHeaderClicked(object sender, RoutedEventArgs e)
{
    ListView listView = sender as ListView;
    if (listView == null)
    {
        return;
    }
    var sorter = _sorting[listView];
    sorter.Sort(e.OriginalSource, listView.Items);
}

In the ViewModel Sorting is removed, so it contains only data as in the Second Generation.

public ObservableCollection<ResultData> SeventhResultData { getset; }

In the XAML part of the View the Click event is removed and the Attached Property is added.

<ListView x:Name="SeventhResultData"
          sort:SortingAttached.IsSorting="True"
   ItemsSource="{Binding SeventhResultData}">
    <ListView.View>
        <GridView>
     <GridViewColumn DisplayMemberBinding="{Binding ResultNumber}">
         <GridViewColumnHeader Content="Number"
                        Padding="2,0,20,0"
          HorizontalContentAlignment="Left" />
     </GridViewColumn>
     <GridViewColumn DisplayMemberBinding="{Binding ResultOutput}">
                <GridViewColumnHeader Content="Output"
                               Padding="2,0,20,0"
                        HorizontalContentAlignment="Left" />
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

In the code-behind of the View nearly all code is removed, only DataContext is there set.

With all that changes Sorting can now be done by only adding Attached Property, but cannot be triggered from ViewModel.

The source code can be downloaded from http://code.msdn.microsoft.com/windowsdesktop/Sorting-a-WPF-ListView-by-027e2303

Further Posts

  1. Sorting a WPF ListView/GridView by clicking on the header
  2. Sort Direction Indicators 
  3. Sort in ViewModel 
  4. Sort Direction Indicators with Adorners
  5. Reusability
  6. More Reusability
  7. Attached Property
  8. Behaviors (Expression Blend)

Wednesday, March 12, 2014

Sorting Evolution (6) - Sorting a WPF ListView/GridView by clicking on the header - More Reusability

Sixth Generation

Now we want to extract the sorting method to an own class. So it can also reused every time we need sorting.

private ListSortDirection _sortDirection;
private string _sortColumnName;
private CollectionViewSource _dataView;

public void SetData<T>(IEnumerable<T> data)
{
  _dataView = new CollectionViewSource();
  _dataView.Source = data;
}

public ListCollectionView GetView()
{
  return (ListCollectionView)_dataView.View;
}

public void Sort(string column)
{
  if (_sortColumnName == column)
  {
    // Toggle sorting direction
    _sortDirection = _sortDirection == ListSortDirection.Ascending ?
                                       ListSortDirection.Descending :
                                       ListSortDirection.Ascending;
  }
  else
  {
    _sortColumnName = column;
    _sortDirection = ListSortDirection.Ascending;
  }

  _dataView.SortDescriptions.Clear();
  _dataView.SortDescriptions.Add(
                new SortDescription(_sortColumnName, _sortDirection));
}

The method SetData<T> expects an IEnumerable<T> that is used as source for a new created CollectionViewSource. The method GetView returns a ListCollectionView, the View of the CollectionViewSource. This can be used for binding in XAML. The method Sort takes a string as parameter that is the name of the property to be used for sorting. If this method is called, the CollectionViewSource will be sorted.

The code of the ViewModel can be reduced by using the new Sorting object.

private Sorting _sorter;
 
public SortingViewModel()
{
  _sorter = new Sorting();
}

private ObservableCollection<ResultData> _sixthResultData;

public ObservableCollection<ResultData> SixthResultData
{
  get
  {
    return _sixthResultData;
  }
  set
  {
    _sixthResultData = value;
    _sorter.SetData(_sixthResultData);
  }
}

public ListCollectionView SixthResultDataView
{
  get
  {
    return _sorter.GetView();
  }
}

public void Sort(string column)
{
  _sorter.Sort(column);
}

The Sorting object is instantiated in the constructor. Setting the data of the ViewModel also set the data of the Sorting object by calling SetData. An own property is used for binding the ListCollectionView retrieved by GetView. The Sort method calls the Sort method of the Sorting object.

The source code can be downloaded from http://code.msdn.microsoft.com/Sorting-a-WPF-ListView-by-a009edcb

Further Posts

  1. Sorting a WPF ListView/GridView by clicking on the header
  2. Sort Direction Indicators 
  3. Sort in ViewModel 
  4. Sort Direction Indicators with Adorners
  5. Reusability
  6. More Reusability
  7. Attached Property
  8. Behaviors (Expression Blend)