WPF - DataGrid - 自动调整列大小的逻辑

时间:2010-12-06 22:25:25

标签: wpf datagrid wpfdatagrid resize

在我的App.xaml文件中,我有以下代码,允许在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>

转换器:

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();
    }
}

但是,DataGrid类在双击列分隔符时提供的自动列大小调整没有考虑到现在通过自动换行可以使标题更小的事实。

例如,假设您有一个包含长标题和短值的列,如下所示:

| Long Header     |
-------------------
| A               |
| B               |
| C               |

双击分隔符将导致:

| Long Header |
---------------
| A           |
| B           |
| C           |

但更好的是,如果它会导致这个:

| Long   |
| Header |
----------
| A      |
| B      |
| C      |

我的问题是:

有没有办法为自动调整大小提供“提示”,让它知道它可以变得更小?或者,我是否必须完全重新实现自动调整大小双击逻辑?

编辑:在Malaek的帮助下,我更新了我的代码如下,但仍然存在一个问题。它与我的MaxHeight和3个或更多单词不能很好地配合。第一次双击会隐藏标题的一部分,但即使列不更改宽度,它也会在第二次双击时重新出现。我将发布我一直在使用的代码。

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代码背后:

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:显然这个问题有点微妙。标题“Last Updated On”触发它,但“Last Updated By”不会触发它。我还在调查。

1 个答案:

答案 0 :(得分:3)

我认为您必须重新模板化DataGridColumnHeader才能访问自动调整大小事件。在auto-size事件中实际发生的唯一事情是DataGridColumn将其Width设置为Auto,因此如果我们可以预览该事件,并用换行符'\ r \ n'替换每个'',我们可以将其换行我们的文字。然后我们可以在SizeChanged事件中将其更改回来。当我尝试这个时,我还为DataGridColumnHeader添加了一个ContentTemplate。

当然,这种方式只有在ColumnHeaders中没有开始的换行符才有效,否则它会搞砸,但希望你仍然能以某种方式使用它。

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

EventHandlers背后的代码。在预览中更改为\ r \ n,然后在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;
    }
}

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;
}