Search code examples
wpfdatagridwpfdatagridrowdetails

DataGrid RowDetails Width problem


Suppose I have a DataGrid that is defined like this

<DataGrid AreRowDetailsFrozen="True"
          ItemsSource="{Binding MyCollection}"
          AutoGenerateColumns="False">
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>
            <Border CornerRadius="5" BorderBrush="Red"
                    BorderThickness="2" Background="Black">
                <TextBlock Foreground="White" Text="{Binding RowDetails}"
                           TextWrapping="Wrap"/>
            </Border>
        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
    <DataGrid.Columns>
        <DataGridTextColumn Header="0" Binding="{Binding Value1}"/>
        <DataGridTextColumn Header="1" Binding="{Binding Value2}"/>
        <DataGridTextColumn Header="2" Binding="{Binding Value3}"/>
        <DataGridTextColumn Header="3" Binding="{Binding Value4}"/>
    </DataGrid.Columns>
</DataGrid>

And looks like this with and without RowDetails

alt text

In the picture to the right I get a very long DataGridRow that never wraps.
Is it possible to get the RowDetails to use the same width as the DataGrid and not effect the Width itself?

Things I have tried that achieves wrapping but not in a satisfying way

  • Set Width or MaxWidth on the Border or the TextBlock. Not very dynamic.
  • Set ScrollViewer.HorizontalScrollBarVisibility="Disabled" on the DataGrid. Not very good when the columns doesn't fit.

Solution

  • This is what I ended up doing. I'd rather use a Property on the DataGrid for this but since no such Property exist I needed a workaround.

    alt text

    First I just used ActualWidth from the parent DataGrid and removed a constant of 9. This worked at first but failed when the vertical scrollbar became visible so I had to use a MultiBinding.

    <DataGrid.RowDetailsTemplate>
        <DataTemplate>
            <Border HorizontalAlignment="Left" CornerRadius="5"
                    BorderBrush="Red" BorderThickness="2" Background="Black">
                <Border.Width>
                    <MultiBinding Converter="{StaticResource RowDetailsWidthMultiConverter}"
                                  ConverterParameter="9">
                        <Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}"
                                 Path="ActualWidth"/>
                        <Binding RelativeSource="{RelativeSource AncestorType={x:Type ScrollViewer}}"
                                 Path="ComputedVerticalScrollBarVisibility"/>
                    </MultiBinding>
                </Border.Width>
                <TextBlock Foreground="White" Text="{Binding RowDetails}" TextWrapping="Wrap"/>
            </Border>
        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
    

    And in the converter I used another constant (16) to compensate for a visible vertical scrollbar (if it's visible).

    public class RowDetailsWidthMultiConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double originalWidth = (double)values[0];
            Visibility verticalScrollbarVisibility = (Visibility)values[1];
            double subtractWidth = System.Convert.ToDouble(parameter);
            double returnWidth = originalWidth - subtractWidth;
            if (verticalScrollbarVisibility == Visibility.Visible)
            {
                return returnWidth - 16;
            }
            return returnWidth;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }
    

    Update

    I improved on the solution a bit, using ActualWidth for the ItemsPresenter rather then DataGrid (where ActualWidth didn't change depending on a visible ScrollBar), thus removing the need for a MultiConverter and two constants.

    <DataGrid.Resources>
        <local:SubtractConstantConverter x:Key="SubtractConstantConverter"/>
    </DataGrid.Resources>
    <DataGrid.RowDetailsTemplate>
        <DataTemplate>
            <Border HorizontalAlignment="Left" CornerRadius="5"
                    BorderBrush="Red" BorderThickness="2" Background="Black"
                    Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsPresenter}},
                                    Path=ActualWidth,
                                    Converter={StaticResource SubtractConstantConverter},
                                    ConverterParameter=6}">
                <TextBlock Foreground="White" Text="{Binding RowDetails}" TextWrapping="Wrap"/>
            </Border>
        </DataTemplate>
    </DataGrid.RowDetailsTemplate>
    

    SubtractConstantConverter

    public class SubtractConstantConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double originalValue = (double)value;
            double subtractValue = System.Convert.ToDouble(parameter);
            return originalValue - subtractValue;
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }