WPF - DataGrid - логика автоматического изменения размера столбцов - PullRequest
1 голос
/ 07 декабря 2010

В моем файле App.xaml приведен следующий код, разрешающий заголовки столбцов «двухэтажных» в DataGrid с.

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: Видимо, проблеманемного более тонкий.Заголовок «Последнее обновление включено» вызывает его, а «Последнее обновление -» нет.Я все еще расследую.

1 Ответ

3 голосов
/ 07 декабря 2010

Я думаю, вам придется повторно шаблонировать DataGridColumnHeader, чтобы получить доступ к событию автоматического изменения размера. Единственное, что на самом деле происходит в событии auto-size, это то, что DataGridColumn получает значение Width, установленное на Auto, поэтому, если мы можем предварительно просмотреть это событие и заменить каждое '' переводом строки '\ r \ n', мы можем получить его для переноса Текст для нас. Затем мы можем изменить его обратно в событии SizeChanged. Я также добавил ContentTemplate для DataGridColumnHeader, когда пытался это сделать.

Конечно, этот способ будет работать только в том случае, если у вас не будет разрывов строк в заголовках 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;
}
...