Angled Headers in the DataGrid

In my last post, I demonstrated how to rotate the text inside a DataGrid header. And I suggested that you might want to actually create angled headers that look something like this.

image 

My friend Cheryl, from the Silverlight UE team, and I decided to try this out. To make this work you need to know that a) the DataGridColumnHeader is a content control and b) you can retemplate the ControlTemplate to look like whatever you want and maintain the functionality.  To make the shape we wanted, we decided to go with a skewed rectangle. At first things seemed pretty straightforward and we were able to get things looking pretty good after re-reading the topic on Transforms and a lot of trial and error with the angles and centers for rotation and skews. A few notes on this:

1. We wanted to skew the Rectangle from the bottom of the header so we bound the CenterY of the SkewTransform to the Height of the Rectangle.

2. We had to both rotate and translate the text to get it in the correct position.

3. I ended up using the LayoutTransform for the rotation in my code since it makes the column width a bit smaller when the Header text is long. You can put both transforms in the RenderTransform but then you can end up with even wider columns since the layout pass assumes you need columns wide enough for the text before the transform. I messed around with trying to get the columns even narrower and if you rotate the text 90 degrees in the LayoutTransform and then rotate it back 45 degrees in the RenderTransform, you get much narrower columns. But it was going to take me a lot more time to figure out the center of the rotation and I think you need another converter because the width of the header isn’t quite wide enough after you skew it, so I’m leaving that undone.

Once we had this working, we realized that we’d hard coded the Height of the columns when we were playing around and we had trouble figuring out how to calculate the height. In the end, I created a MultiBinding where I pass in the DataGrid (so that you can get the Header values off the columns) and all the font information. Then I calculate the length of the longest Header string using a FormattedText object and pad that a bit for the height.

The one thing that still doesn’t work is that the hit region for the headers doesn’t change. So, you have to click low in the header to sort or drag the header. I’m going to ask around and see if I can get a solution to this and I’ll post it if I do. The other thing is that the drag visual is still the old header style too.  You can override that with the DragIndicatorStyle (I believe) but I haven’t tested it.

Here is the new ControlTemplate. If you want to see the entire XAML and code behind (VB and C#), I’ve posted those here.

<ControlTemplate TargetType="DataGridColumnHeader">

<Grid ShowGridLines="True">

<Rectangle Name="HeaderRect" Width="{TemplateBinding Width}" Fill="LightBlue" Stroke="Black"

StrokeThickness="1" >

<Rectangle.Height>

<MultiBinding Converter="{StaticResource HeightConverter}">

<Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" />

<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontSize" />

<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontFamily" />

<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontStyle" />

<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontStretch" />

<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontWeight" />

</MultiBinding>

</Rectangle.Height>

<Rectangle.RenderTransform>

<SkewTransform CenterX="0" CenterY="{Binding ElementName=HeaderRect, Path=Height}"

AngleX="-45" AngleY="0" />

</Rectangle.RenderTransform>

</Rectangle>

<ContentPresenter Content="{TemplateBinding Content}" VerticalAlignment="Bottom"

HorizontalAlignment="Left" >

<ContentPresenter.LayoutTransform>

<RotateTransform Angle="-45"/>

</ContentPresenter.LayoutTransform>

<ContentPresenter.RenderTransform>

<TranslateTransform X="{Binding RelativeSource={RelativeSource TemplatedParent},

Path=ActualWidth, Mode=OneWay, Converter={StaticResource WidthConverter}}" />

</ContentPresenter.RenderTransform>

</ContentPresenter>

</Grid>

</ControlTemplate>

In addition to some of the issues I mentioned, I wasn’t very thorough about checking the original template for all the TemplateBindings so there may be some other TemplateBindings you need to set up if you want to be able to set a value on the DataGrid.   For the Silverlight version, see Cheryl’s post.

Margaret