Thursday, February 13, 2014

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

Fifth Generation

To reduce and to reuse the code in code-behind of the View we can extract it. The extracted code is moved in an own class that can be reused every time we want to add sorting indicators to a GridViewColumnHeader of a ListView.

private ListSortDirection _sortDirection;
private GridViewColumnHeader _sortColumn;

public string SetAdorner(object columnHeader)
{
  GridViewColumnHeader column = columnHeader as GridViewColumnHeader;
  if (column == null)
  {
    return null;
  }

  // Remove arrow from previously sorted header
  if (_sortColumn != null)
  {
    var adornerLayer = AdornerLayer.GetAdornerLayer(_sortColumn);
    try { adornerLayer.Remove((adornerLayer.GetAdorners(_sortColumn))[0]); }
    catch { }
  }

  if (_sortColumn == column)
  {
    // Toggle sorting direction
    _sortDirection = _sortDirection == ListSortDirection.Ascending ?
                                       ListSortDirection.Descending :
                                       ListSortDirection.Ascending;
  }
  else
  {
    _sortColumn = column;
    _sortDirection = ListSortDirection.Ascending;
  }

  var sortingAdorner = new SortingAdorner(column, _sortDirection);
  AdornerLayer.GetAdornerLayer(column).Add(sortingAdorner);
          
  string header = string.Empty;

  // if binding is used and property name doesn't match header content
  Binding b = _sortColumn.Column.DisplayMemberBinding as Binding;
  if (b != null)
  {
    header = b.Path.Path;
  }

  return header;
}


The new class can be initiated in the constructor of the View.

private Sorting _sorter;

public SortingView()
{
  InitializeComponent();
  DataContext = new SortingViewModel();
  _sorter = new Sorting();
}

private void FifthResultDataViewClick(object sender, RoutedEventArgs e)
{
  string header = _sorter.SetAdorner(e.OriginalSource);
  var viewModel = DataContext as SortingViewModel;
  viewModel.Sort(header);
}

The method SetAdorner is called in the GridViewCoumnHeader.Click event. In this method the Adorner is set. The method returns the property name that is binded to the column that should be sorted. The name is passed to the ViewModel that is sorting the column.

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

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)

Tuesday, February 4, 2014

Sorting Evolution (4) - Sorting a WPF ListView/GridView by clicking on the header - Sort Direction Indicators with Adorners

Fourth Generation

To enhance the sorting of WPF ListView I wanted to extract the resource that is drawing the small glyphs indicating sorting direction. Therefore I could use a ResourceDictionary and moving into it the DataTemplates defined in the Second Generation. But I decided to use an Adorner to achieve the goal. Adorners can be used to overlay a visual element over other elements.

public class SortingAdorner : Adorner
{
  private static Geometry _arrowUp = Geometry.Parse("M 5,5 15,5 10,0 5,5");
  private static Geometry _arrowDown = Geometry.Parse("M 5,0 10,5 15,0 5,0");
  private Geometry _sortDirection;
 
  public SortingAdorner(GridViewColumnHeader adornedElement, 
                   ListSortDirection sortDirection) : base(adornedElement)
  {
    _sortDirection = sortDirection == ListSortDirection.Ascending ?
                                                         _arrowUp :
                                                         _arrowDown;
  }
 
  protected override void OnRender(System.Windows.Media.DrawingContext     
                                                                  drawingContext)
  {
    double x = AdornedElement.RenderSize.Width - 20;
    double y = (AdornedElement.RenderSize.Height - 5) / 2;
 
    if (x >= 20)
    {
      // Right order of the statements is important
      drawingContext.PushTransform(new TranslateTransform(x, y));
      drawingContext.DrawGeometry(Brushes.Black, null, _sortDirection);
      drawingContext.Pop();
    }
  }
}

The SortingAdorner implements the abstract class Adorner. The constructor calls the base constructor of the Adorner class and passes the adorned element (GridViewColumnHeader) to it. Furthermore the sorting direction is set in the constructor. The small arrows are set in the overridden OnRender method. Also the position and further attributes are calculated in this method. The arrows will only set if there is enough space for it.

The ViewModel is the same as in the Third Generation.

The Resources in the XAML part of the View were removed and the layout of column headers were slightly changed so that changing the size of the header has no negative impact.

<ListView ItemsSource="{Binding FourthResultDataView}"
          GridViewColumnHeader.Click="FourthResultDataViewClick">
  <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>

With that code the small glyphs occupy space on the right of the header name

If the width of the column is reduced the arrow covers the name

If the width is further reduced the arrow will disappear

Another solution could be to change to Adorner to view small glyphs in Windows Explorer style


The code-behind of the View is nearly the same as in the Third Generation, but the setting of the HeaderTemplate is replaced by adding an Adorner to the Adorner layer.

private ListSortDirection _sortDirection;
private GridViewColumnHeader _sortColumn;
 
private void FourthResultDataViewClick(object sender, RoutedEventArgs e)
{
  GridViewColumnHeader column = e.OriginalSource as GridViewColumnHeader;
  if (column == null)
  {
    return;
  }
 
  // Remove arrow from previously sorted header
  if (_sortColumn != null)
  {
    var adornerLayer = AdornerLayer.GetAdornerLayer(_sortColumn);
    try { adornerLayer.Remove((adornerLayer.GetAdorners(_sortColumn))[0]); }
    catch { }
  }
 
  if (_sortColumn == column)
  {
    // Toggle sorting direction
    _sortDirection = _sortDirection == ListSortDirection.Ascending ?
                                       ListSortDirection.Descending :
                                       ListSortDirection.Ascending;
  }
  else
  {
    _sortColumn = column;
    _sortDirection = ListSortDirection.Ascending;
  }
 
  var sortingAdorner = new SortingAdorner(column, _sortDirection);
  AdornerLayer.GetAdornerLayer(column).Add(sortingAdorner);
  string header = string.Empty;
 
  // if binding is used and property name doesn't match header content
  Binding b = _sortColumn.Column.DisplayMemberBinding as Binding;
  if (b != null)
  {
    header = b.Path.Path;
  }
 
  var viewModel = DataContext as SortingViewModel;
  viewModel.Sort(header);
}

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

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)