May 10
More Attached Goodness
Here's the code, read on for the explanation.
So hopefully my last series on enabling MultiSelect checkboxes with attached properties reinforced the idea that the WPF team has been trying to bring home: subclassing a control should be used in extreme circumstances, because most enhancements can be done using Styles, Templates, and as I just showed Attached Properties. Here's yet another trick that we're using in a custom project.
For some reason the ATC team didn't provide an IsSortable property on the GridView...all though they did show how simple it is to add sorting to a gridview using code behind. Well you know me, I like simple. So I created an Attached Property that uses the Plover Remora Pattern to add sorting to a ListView (with a Gridview as its View) without any code-behind (other than the code that's in the attached property). The final XAML to use this functionality is as simple as this:
<ListView
Util:WPFUtils.IsGridSortable="True"
Width="Auto"
Height="Auto"
ItemsSource="{Binding MyItems}"
SelectionMode="Multiple"
Margin="5">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Department" DisplayMemberBinding="{Binding Department}"/>
<GridViewColumn Header="Extension" DisplayMemberBinding="{Binding Extension}"/>
</GridView>
</ListView.View>
</ListView>
Basically all you have to do to make your GridView sortable is set the attached property IsGridSortable to true and it handles the rest. Since we already know how the Remora property works (an attached property adds functionality to the Control it's attached to during the PropertyChangedCallback) We'll cut straight to the code for our PropertyChangedCallback. (Full code will be available at the end of this posting).
private static void OnRegisterSortableGrid(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
ListView grid = sender as ListView;
if (grid != null)
{
RegisterSortableGridView(grid, args);
}
}
private static void RegisterSortableGridView(ListView grid,
DependencyPropertyChangedEventArgs args)
{
if (args.NewValue is Boolean && (Boolean)args.NewValue)
{
grid.AddHandler(GridViewColumnHeader.ClickEvent, GridViewColumnHeaderClickHandler);
}
else
{
grid.RemoveHandler(GridViewColumnHeader.ClickEvent, GridViewColumnHeaderClickHandler);
}
}
The callback basically checks if the property has been set on a listview (I am of course adding a check for if it's set on a PowerGrid) and if so calls the RegisterSortableGridView function. This function adds a custom handler for the GridViewColumnHeader.Click Event or removes it based on the value assigned to the property by the user. The handler works pretty much the same way as the ATC sample with a few differences.
if (header != null)
{
ListSortDirection sortDirection;
GridViewColumnHeader tmpHeader = GetLastSorted(lv);
if (tmpHeader != null)
tmpHeader.Column.HeaderTemplate = null;
if (header != tmpHeader)
sortDirection = ListSortDirection.Ascending;
else
{
ListSortDirection tmpDirection = GetLastSortDirection(lv);
if (tmpDirection == ListSortDirection.Ascending)
sortDirection = ListSortDirection.Descending;
else
sortDirection = ListSortDirection.Ascending;
}
You'll see I highlighted calls to two functions GetLastSorted(lv); and GetLastSortDirection(lv). These are Getters for Read-only Attached Dependency Properties LastSorted and LastSortDirection, respectively (and the SDK said they aren't useful), that are set on the ListView during a sort operation...if this is the first sort operation of course they'll be null. The other cool feature...
switch (sortDirection)
{
case ListSortDirection.Ascending: resourceTemplateName = "HeaderTemplateSortAsc"; break;
case ListSortDirection.Descending: resourceTemplateName = "HeaderTemplateSortDesc"; break;
}
DataTemplate tmpTemplate = lv.TryFindResource(resourceTemplateName) as DataTemplate;
if (tmpTemplate != null)
{
header.Column.HeaderTemplate = tmpTemplate;
}
If the user has declared HeaderTemplateSortAsc and/or HeaderTemplateSortDesc in their resource tree, it will be set on the GridViewColumnHeader. Today's upload only includes the cs file for the WPFUtils class. I'll pull it out of our main app and into it's own project later.
Again, this shows that WPF Controls can be customized without subclassing them. And without codebehind for the UI of the code cluttering your business logic code. Next time, I want to talk about MVC with WPF.