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.


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.


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.

Tuesday, February 4, 2014

Sorting Evolution (4) - Sorting a WPF ListView/GridView by clicking on the header - 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 another 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 place 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 cover 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

Monday, December 30, 2013

WPF Binding of DataGrid Column Header in XAML

Dynamically binding to a column header of a DataGrid is not a transparently task. The problem is that the column header is not inheriting from FrameworkElement.

A simple solution to that problem is presented on the Developer Code Samples Gallery (Binding of DataGrid column header) and the TechNet Wiki (Binding of DataGrid Column Header).

Saturday, December 28, 2013

Identifying and Resolving Shortcuts/Links of Files and Folders

In Windows file shortcuts can be identified by the file extension. Default file extension for file and folder shortcuts is ".lnk". But also ".appref-ms" is used as file extension for file shortcuts. Furthermore could it be possible that installed applications use the default shortcut file extension for its own application dependent files. So using file extensions as evidence for shortcut is not sufficient.

A solution to identify and resolve Windows shortcuts is presented on the Developer Code Samples Gallery (Identifying and Resolving Shortcuts/Links of files and folders) and the TechNet Wiki (Identifying and Resolving Shortcuts/Links of Files and Folders).

Thursday, December 26, 2013

Sorting Evolution (3) - Sorting a WPF ListView/GridView by clicking on the header - Sort in ViewModel

Third Generation


After reading Messenger and View Services in MVVM by Laurent Bugnion, I thought about moving sorting again to the ViewModel. The View will still handle the visual elements, while the ViewModel has again the responsibility for sorting. So sorting can also be triggered by the business logic, if needed.


The ViewModel is nearly the same as in the First Generation, but the RelayCommand is not needed.

private ObservableCollection<ResultData> _thirdResultData;
private CollectionViewSource _thirdResultDataView;
private string _sortColumn;
private ListSortDirection _sortDirection;
 
public ObservableCollection<ResultData> ThirdResultData
{
  get
  {
    return _thirdResultData;
  }
  set
  {
    _thirdResultData = value;
    _thirdResultDataView = new CollectionViewSource();
    _thirdResultDataView.Source = _thirdResultData;
  }
}

public ListCollectionView ThirdResultDataView
{
  get
  {
    return (ListCollectionView)_thirdResultDataView.View;
  }
}

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

  _thirdResultDataView.SortDescriptions.Clear();
  _thirdResultDataView.SortDescriptions.Add(
                            new SortDescription(_sortColumn, _sortDirection));
}

The XAML part of the View is the same as in the Second Generation.

<UserControl.Resources>
  <DataTemplate x:Key="ArrowUp">
    <DockPanel>
      <TextBlock HorizontalAlignment="Center"
                 Text="{Binding}" />
      <Path VerticalAlignment="Center"
            Fill="Black"
            Data="M 5,5 15,5 10,0 5,5" />
    </DockPanel>
  </DataTemplate>
  <DataTemplate x:Key="ArrowDown">
    <DockPanel>
      <TextBlock HorizontalAlignment="Center"
                 Text="{Binding}" />
      <Path VerticalAlignment="Center"
            Fill="Black"
            Data="M 5,0 10,5 15,0 5,0" />
    </DockPanel>
  </DataTemplate>
</UserControl.Resources>
 
<Grid>
  <ListView ItemsSource="{Binding ThirdResultDataView}"
            GridViewColumnHeader.Click="ThirdResultDataViewClick">
    <ListView.View>
      <GridView>
        <GridViewColumn DisplayMemberBinding="{Binding ResultNumber}">
          <GridViewColumnHeader Content="Number" />
        </GridViewColumn>
        <GridViewColumn DisplayMemberBinding="{Binding ResultOutput}">
          <GridViewColumnHeader Content="Output" />
        </GridViewColumn>
      </GridView>
    </ListView.View>
  </ListView>
</Grid>

The code-behind of the View is nearly the same as in the Second Generation, but the call of the sorting method in the ViewModel is added.

private ListSortDirection _sortDirection;
private GridViewColumnHeader _sortColumn;

private void ThirdResultDataViewClick(object sender, RoutedEventArgs e)
{
  GridViewColumnHeader column = e.OriginalSource as GridViewColumnHeader;
  if (column == null)
  {
    return;
  }

  if (_sortColumn == column)
  {
    // Toggle sorting direction
    _sortDirection = _sortDirection == ListSortDirection.Ascending ?
                                       ListSortDirection.Descending :
                                       ListSortDirection.Ascending;
  }
  else
  {
    // Remove arrow from previously sorted header
    if (_sortColumn != null)
    {
      _sortColumn.Column.HeaderTemplate = null;
      _sortColumn.Column.Width = _sortColumn.ActualWidth - 20;
    }

    _sortColumn = column;
    _sortDirection = ListSortDirection.Ascending;
    column.Column.Width = column.ActualWidth + 20;
  }
 
  if (_sortDirection == ListSortDirection.Ascending)
  {
    column.Column.HeaderTemplate = Resources["ArrowUp"as DataTemplate;
  }
  else
  {
    column.Column.HeaderTemplate = Resources["ArrowDown"as DataTemplate;
  }

  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-ce9cf6d7

Monday, December 23, 2013

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

Second Generation


After I had implemented sorting (First Generation) of WPF ListView, I wanted to add small glyphs that indicates the sorted column and the sorting direction. In my application sorting is only used if the user triggers it on the view and it changes only the view and not the data. So I decided to move sorting code to code-behind what made it easier to add the small sorting direction arrows to a column header.

First I defined the small glyphs that are used to indicate the sorting direction.

<UserControl.Resources>
  <DataTemplate x:Key="ArrowUp">
    <DockPanel>
      <TextBlock HorizontalAlignment="Center"
                 Text="{Binding}" />
      <Path VerticalAlignment="Center"
            Fill="Black"
            Data="M 5,5 15,5 10,0 5,5" />
    </DockPanel>
  </DataTemplate>
  <DataTemplate x:Key="ArrowDown">
    <DockPanel>
      <TextBlock HorizontalAlignment="Center"
                 Text="{Binding}" />
      <Path VerticalAlignment="Center"
            Fill="Black"
            Data="M 5,0 10,5 15,0 5,0" />
    </DockPanel>
  </DataTemplate>
</UserControl.Resources>

Then I changed the View. I removed the commanding and added a click event handler.

<ListView x:Name="SecondResultData"
          ItemsSource="{Binding SecondResultData}"
          GridViewColumnHeader.Click="SecondResultDataViewClick">
  <ListView.View>
    <GridView>
      <GridViewColumn DisplayMemberBinding="{Binding ResultNumber}">
        <GridViewColumnHeader Content="Number" />
      </GridViewColumn>
      <GridViewColumn DisplayMemberBinding="{Binding ResultOutput}">
        <GridViewColumnHeader Content="Output" />
      </GridViewColumn>
    </GridView>
  </ListView.View>
</ListView>

The ItemsSource is still binded to a property in the ViewModel.

public ObservableCollection<ResultData> SecondResultData { getset; }

The click event handler is implemented in code-behind.

private ListSortDirection _sortDirection;
private GridViewColumnHeader _sortColumn;
 
private void SecondResultDataViewClick(object sender, RoutedEventArgs e)
{
  GridViewColumnHeader column = e.OriginalSource as GridViewColumnHeader;
  if (column == null)
  {
    return;
  }

  if (_sortColumn == column)
  {
    // Toggle sorting direction
    _sortDirection = _sortDirection == ListSortDirection.Ascending ?
                                       ListSortDirection.Descending :
                                       ListSortDirection.Ascending;
  }
  else
  {
    // Remove arrow from previously sorted header
    if (_sortColumn != null)
    {
      _sortColumn.Column.HeaderTemplate = null;
      _sortColumn.Column.Width = _sortColumn.ActualWidth - 20;
    }

    _sortColumn = column;
    _sortDirection = ListSortDirection.Ascending;
    column.Column.Width = column.ActualWidth + 20;
  }

  if (_sortDirection == ListSortDirection.Ascending)
  {
    column.Column.HeaderTemplate = Resources["ArrowUp"as DataTemplate;
  }
  else
  {
    column.Column.HeaderTemplate = Resources["ArrowDown"as DataTemplate;
  }

  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;
  }
 
  ICollectionView resultDataView = CollectionViewSource.GetDefaultView(
                                             SecondResultData.ItemsSource);
  resultDataView.SortDescriptions.Clear();
  resultDataView.SortDescriptions.Add(
                              new SortDescription(header, _sortDirection));
}

The click event handler set the HeaderTemplate of the columns. Therefore the previously defined glyphs are used. The column that is used to sort gets an arrow that indicates the sorting direction. The arrow is removed from previously sorted column, if necessary.



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