For anyone who’s done any WPF control retemplating, it can sometimes be a daunting task. Some controls seem to retemplate with ease, while others can have a myriad of nested styles, with certain parts seeming to appear where you don’t expect them. Today, I ran into an interesting situation with the WPF Toolkit’s DataGrid – retemplating the column header sorting arrows.
As it turns out, the arrows for column sorting live in the control DataGridHeaderBorder. If we inspect the DataGrid.DataGridColumnHeader style in Blend…
…notice there’s no placeholder for the arrows. Attempting to edit the default style for the Border won’t work either – there is no style to be found. There is no ControlTemplate to edit either, since DataGridHeaderBorder inherits from Border, which in turn inherits from FrameworkElement, and not Control. (Remember, Control defines the ControlTemplate property)
So where are the arrows defined then? Well, inspecting the source for the DataGridHeaderBorder…
/// <summary>
/// Called when this element should re-render.
/// </summary>
protected override void OnRender(DrawingContext dc)
{
if (UsingBorderImplementation)
{
// Revert to the Border implementation
base.OnRender(dc);
}
else
{
// Choose the appropriate rendering based on the current theme
switch (Theme)
{
// Omitted for brevity's sake...
// A switch here calls RenderAeroNormalColor.
}
}
private void RenderAeroNormalColor(DrawingContext dc)
{
if (isSorted && (size.Width > 14.0) && (size.Height > 10.0))
{
// Draw the sort arrow
TranslateTransform positionTransform = new TranslateTransform((size.Width - 8.0) * 0.5, 1.0);
positionTransform.Freeze();
dc.PushTransform(positionTransform);
bool ascending = (sortDirection == ListSortDirection.Ascending);
PathGeometry arrowGeometry = (PathGeometry)GetCachedFreezable(ascending ? (int)AeroFreezables.ArrowUpGeometry : (int)AeroFreezables.ArrowDownGeometry);
// Actual drawing here, omitted for brevity's sake...
}
}
If you snoop through this source (in the toolkit), you’ll notice the arrows are drawn in the OnRender() method of the DataGridHeaderBorder. In my humble opinion, I don’t think this is the most intuitive way to pull this effect off, but I’m sure there was a sound reason for it. In any case, if you’ve got your own sorting arrows that you wish to use, the easiest (and only way, I believe) to do this is to adjust their visibility by using the Triggers of the DataGridColumnHeader, namely the SortDirection property.
Special note: SortDirection is a nullable property – it is set to null when there is no sorting applied to the column. Keep this in mind when templating the control.
For this demonstration, I’ve blown away the default DataGridHeaderBorder. I’ll use a regular Border, with a ContentPresenter within it (for the column header contents – don’t forget this), and two TextBlocks to indicate the sort direction. Remember, you aren’t limited to TextBlocks – you can use whatever you want to indicate sort direction. You can even use Storyboards that fire on the EnterAction property of the trigger to provide animations.
First, I created a style on the DataGridColumnHeader of a fresh DataGrid, and defined it in Window1.xaml. I blew away the default style and put in a Border containing a StackPanel, which contains the ContentPresenter and our two TextBlocks.
The TextBlocks are to be Collapsed by default, since the default state of a column is no sorting.
The reason I picked a StackPanel to contain the ContentPresenter and the sorting arrows is that I wanted to whip up a demo fairly quickly, and this is the fastest way to get the control to automatically resize itself to prevent waste of dead-space from the arrows. You can pull off the hiding of the space used by the arrows by using appropriate autosizing of grid columns, etc., a topic of which I’ll cover in a future article.
Next, we can apply our triggers to show the user what direction they are sorting. In the above screengrab, I’ve already added three Property triggers for SortDirection: Ascending, Descending, and null. Null is used for when there is no sorting applied, and the other two are self-explanatory. So, depending on which value is currently set will define which arrows are visible.
With SortDirection = Ascending selected, I’ve set the Visibility property of the Up TextBlock to be visible, and for the SortDirection = Descending, the Dn TextBlock is visible. When null, no changes occur from the default style, which is to say both TextBlocks are Collapsed. The end result looks like this…
which goes to… 
… and finally goes to…
