Reputation: 471
In my App.xaml
file, I have the following code to allow "double-decker" column headers in DataGrid
s.
XAML:
<adv:ColumnHeaderFontSizeToMaxHeightConverter x:Key="columnHeaderFontSizeToMaxHeightConverter" />
<DataTemplate x:Key="WrappingDataGridColumnHeaderTemplate" DataType="{x:Type sys:String}">
<TextBlock TextWrapping="WrapWithOverflow"
Text="{Binding}"
ToolTip="{Binding}"
MaxHeight="{Binding Path=FontSize, Mode=OneWay,
RelativeSource={RelativeSource Self},
Converter={StaticResource columnHeaderFontSizeToMaxHeightConverter} }" />
</DataTemplate>
Converter:
internal class ColumnHeaderFontSizeToMaxHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
Debug.Assert(value.GetType() == typeof(double));
// We want to have up to 2 lines of text here plus a little bit of space for margins, etc
// WPF will automatically use the smallest height required
return (double)value * 2.9;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
Debug.Assert(false);
throw new NotImplementedException();
}
}
However, the automatic column resizing that the DataGrid
class provides on double clicking the column separators does not take into account the fact that the headers can now get even smaller with word wrapping.
For example, say you have a column with a long header and short values like this:
| Long Header |
-------------------
| A |
| B |
| C |
Double clicking on the separator will result in this:
| Long Header |
---------------
| A |
| B |
| C |
But what what would be even better is if it would result in this:
| Long |
| Header |
----------
| A |
| B |
| C |
My question is this:
Is there a way to provide a "hint" to the automatic resizing to let it know that it can go even smaller? Or, will I have to completely reimplement the automatic-resizing-on-double-click logic?
EDIT: With Malaek's help, I've updated my code as follows, but one problem still remains. It doesn't play nicely with my MaxHeight and 3 or more words. The first double click has part of the header hidden, yet it reappears on the second double click even though the column doesn't change width. I'll post the code I've been using shortly.
The DataGrid xaml:
<DataGrid.Resources>
...
<Style TargetType="{x:Type DataGridColumnHeader}"
BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="ContentTemplate" Value="{StaticResource WrappingDataGridColumnHeaderTemplate}" />
<EventSetter Event="SizeChanged" Handler="DataGridColumnHeader_SizeChanged"/>
<EventSetter Event="Loaded" Handler="DataGridColumnHeader_Loaded" />
</Style>
...
</DataGrid.Resources>
DataGrid code behind:
private void RightThumb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Thumb thumb = sender as Thumb;
DataGridColumnHeader dataGridColumnHeader = VisualTreeHelpers.GetVisualParent<DataGridColumnHeader>(thumb);
DataGridColumn column = dataGridColumnHeader.Column;
UpdateColumnForResize(column);
}
private void LeftThumb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Thumb thumb = sender as Thumb;
DataGridColumnHeader dataGridColumnHeader = VisualTreeHelpers.GetVisualParent<DataGridColumnHeader>(thumb);
DataGridColumn column = this.Columns.FirstOrDefault(
c => c.DisplayIndex == dataGridColumnHeader.Column.DisplayIndex - 1);
UpdateColumnForResize(column);
}
private void UpdateColumnForResize(DataGridColumn column)
{
if (column != null)
{
column.Width = column.Width.DisplayValue;
string header = (string)column.Header;
if (header.Contains("\r\n"))
return;
int middle = header.Length / 2;
int closestToMiddle = -1;
for (int i = 0; i < header.Length; ++i)
{
if (header[i] == ' ')
{
if (closestToMiddle == -1)
closestToMiddle = i;
else if (Math.Abs(i - middle) < Math.Abs(closestToMiddle - middle))
closestToMiddle = i;
}
}
if (closestToMiddle != -1)
{
StringBuilder newHeader = new StringBuilder(header);
newHeader.Replace(" ", "\r\n", closestToMiddle, 1);
column.Header = newHeader.ToString();
}
}
}
private void DataGridColumnHeader_SizeChanged(object sender, SizeChangedEventArgs e)
{
DataGridColumnHeader columnHeader = sender as DataGridColumnHeader;
DataGridColumn column = columnHeader.Column;
if (column != null && column.Header.ToString().IndexOf("\r\n") >= 0)
{
column.Header = column.Header.ToString().Replace("\r\n", " ");
column.Width = column.Width.DisplayValue;
}
}
private void DataGridColumnHeader_Loaded(object sender, EventArgs e)
{
DataGridColumnHeader columnHeader = sender as DataGridColumnHeader;
Thumb thumb = columnHeader.Template.FindName("PART_RightHeaderGripper", columnHeader) as Thumb;
if (thumb != null)
thumb.PreviewMouseDoubleClick += RightThumb_PreviewMouseDoubleClick;
thumb = columnHeader.Template.FindName("PART_LeftHeaderGripper", columnHeader) as Thumb;
if (thumb != null)
thumb.PreviewMouseDoubleClick += LeftThumb_PreviewMouseDoubleClick;
}
EDIT2: Apparently the problem is a little more subtle. The header "Last Updated On" triggers it, but "Last Updated By" doesn't. I'm still investigating.
Upvotes: 1
Views: 4540
Reputation: 84647
I think you'll have to re-template the DataGridColumnHeader in order to get access to the auto-sizing event. The only thing that's actually happening in the auto-size event is that the DataGridColumn get its Width set to Auto so if we can preview that event, and replace every ' ' with a linebreak '\r\n' we can get it to wrap the Text for us. Then we can change it back in the SizeChanged event. I also added a ContentTemplate for the DataGridColumnHeader when I was trying this out.
Ofcourse, this way will only work if you'll have no linebreaks in the ColumnHeaders to begin with, otherwise it'll get messed up but hopefully you can still use it somehow.
<DataGrid ...>
<DataGrid.Resources>
<Style x:Key="ColumnHeaderGripperStyle" TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeWE"/>
<EventSetter Event="PreviewMouseDoubleClick" Handler="Thumb_PreviewMouseDoubleClick"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}">
<EventSetter Event="SizeChanged" Handler="DataGridColumnHeader_SizeChanged"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding}"></TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Microsoft_Windows_Themes:DataGridHeaderBorder BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" IsClickable="{TemplateBinding CanUserSort}" IsPressed="{TemplateBinding IsPressed}" IsHovered="{TemplateBinding IsMouseOver}" Padding="{TemplateBinding Padding}" SortDirection="{TemplateBinding SortDirection}" SeparatorBrush="{TemplateBinding SeparatorBrush}" SeparatorVisibility="{TemplateBinding SeparatorVisibility}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Microsoft_Windows_Themes:DataGridHeaderBorder>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ColumnHeaderGripperStyle}"/>
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource ColumnHeaderGripperStyle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<!-- ... -->
</DataGrid>
Code behind EventHandlers. Change to \r\n in preview and change back to ' ' in SizeChanged.
void Thumb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Thumb thumb = sender as Thumb;
DataGridColumnHeader dataGridColumnHeader = VisualTreeHelpers.GetVisualParent<DataGridColumnHeader>(thumb);
DataGridColumn column = dataGridColumnHeader.Column;
if (column != null)
{
column.Width = column.Width.DisplayValue;
column.Header = column.Header.ToString().Replace(" ", "\r\n");
}
}
void DataGridColumnHeader_SizeChanged(object sender, SizeChangedEventArgs e)
{
DataGridColumnHeader columnHeader = sender as DataGridColumnHeader;
DataGridColumn column = columnHeader.Column;
if (column != null && column.Header.ToString().IndexOf("\r\n") >= 0)
{
column.Header = column.Header.ToString().Replace("\r\n", " ");
column.Width = column.Width.DisplayValue;
}
}
An implementation of GetVisualParent
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
// iteratively traverse the visual tree
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
Upvotes: 3