wpfxamlexpandergridsplitter

Resizing Grid with Expander not working after GridSplitter moved


Background

I'm trying to create a control with a file explorer at the top and TreeView at the bottem separated by a GridSplitter. The user can change the size of the file explorer, but not beyond a minimum value. When the Expander is collapsed, the TreeView gets all the room. When the Expander is expanded it goes back to the size that it was before it collapsed.

Background

Example

Using this post I tried to make a working example.

<Grid Margin="3">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="3*" />
    </Grid.RowDefinitions>

    <Border BorderBrush="Red" BorderThickness="3">
        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition>
                    <RowDefinition.Style>
                        <Style TargetType="{x:Type RowDefinition}">
                            <Setter Property="Height" Value="*" />
                            <Setter Property="MinHeight" Value="150" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding ElementName=MyExpander, Path=IsExpanded}" Value="False">
                                    <Setter Property="Height" Value="24" />
                                    <Setter Property="MinHeight" Value="24" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </RowDefinition.Style>
                </RowDefinition>
            </Grid.RowDefinitions>

            <Button Grid.Row="0" Margin="3" Content="Button 1" HorizontalAlignment="Stretch"/>

            <Expander x:Name="MyExpander" Grid.Row="1" Margin="3" IsExpanded="True">
                <Border BorderBrush="Blue" BorderThickness="3"/>
            </Expander>
        </Grid>
    </Border>

    <GridSplitter
        Grid.Row="1"
        Height="3"
        HorizontalAlignment="Stretch" 
        Margin="3,0,3,0"/>

    <Border Grid.Row="2" BorderBrush="Green" BorderThickness="3"/>  
</Grid>

The resizing works at first when I collapse and expand the GridSplitter.

Example

Problem 1

After the GridSplitter is moved, the resizing after collapse doesn't work anymore. Problem 1

Problem 2

I can resize beyond the MinHeight of 150 that I put in the RowDefinition of the Expander row. Problem 2

How to solve these problems?


Solution

  • There are a couple reasons why your current XAML doesn't work.

    1. Your MinHeight is set on an element nested inside of a panel that the GridSplitter doesn't see. So it will never honor the size.
    2. The GridSplitter overrides the Height property of its adjacent RowDefinitions and because of this, you cannot use Style Triggers to manipulate the Height in order to get dynamic resizing when your Expander expands and collapses
    3. There is no simple way to restore the previous Height of the RowDefinition when the Expander is collapsed and expanded again.

    Here is a working example, where I've restructured your XAML so that the GridSplitter can obey the MinHeight of your Expander.

    I've also introduced a simple attached behavior class that will react to the Expander's Expanded and Collapsed events and will set the appropriate target RowDefinition's Height to Auto so that you get the auto resizing that you want.

    The attached behavior class also persists the previous Height of the target RowDefinition and will restore it when the expander is expanded.

    XAML

    <Grid Margin="3">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition x:Name="ExpanderRow">
                <RowDefinition.Style>
                    <Style TargetType="{x:Type RowDefinition}">
                        <Setter Property="MinHeight" Value="150" />
                        <Style.Triggers>
                            <DataTrigger
                                Binding="{Binding ElementName=MyExpander, Path=IsExpanded}"
                                Value="False">
                                <Setter Property="MinHeight" Value="40" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </RowDefinition.Style>
            </RowDefinition>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
    
        <Border
            Grid.Row="0"
            Grid.RowSpan="2"
            BorderBrush="Red"
            BorderThickness="3" />
    
        <Button
            Grid.Row="0"
            Margin="6"
            HorizontalAlignment="Stretch"
            Content="Button 1" />
    
        <Expander x:Name="MyExpander"
            Grid.Row="1"
            Margin="6"
            local:ExpanderRowHeightBehavior.IsEnabled="True"
            local:ExpanderRowHeightBehavior.TargetRow="{Binding ElementName=ExpanderRow}"
            IsExpanded="True">
            <Border
                BorderBrush="Blue"
                BorderThickness="3" />
        </Expander>
    
        <GridSplitter
            Grid.Row="2"
            Height="3"
            Margin="3,0,3,0"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Center" />
    
        <Border
            Grid.Row="3"
            BorderBrush="Green"
            BorderThickness="3" />
    </Grid>
    

    ExpanderRowHeightBehavior.cs

    using System.Windows;
    using System.Windows.Controls;
    
    namespace SO
    {
        public static class ExpanderRowHeightBehavior
        {
            #region IsEnabled (Attached Property)
            public static readonly DependencyProperty IsEnabledProperty =
                DependencyProperty.RegisterAttached(
                    "IsEnabled",
                    typeof(bool),
                    typeof(ExpanderRowHeightBehavior),
                    new PropertyMetadata(false, OnIsEnabledChanged));
    
            public static bool GetIsEnabled(DependencyObject obj)
            {
                return (bool)obj.GetValue(IsEnabledProperty);
            }
    
            public static void SetIsEnabled(DependencyObject obj, bool value)
            {
                obj.SetValue(IsEnabledProperty, value);
            }
    
            private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                if (!(d is Expander expander)) return;
    
                expander.Collapsed += OnCollapsed;
                expander.Expanded += OnExpanded;
    
            }
            #endregion
    
            #region TargetRow (Attached Property)
            public static readonly DependencyProperty TargetRowProperty =
                DependencyProperty.RegisterAttached(
                    "TargetRow",
                    typeof(RowDefinition),
                    typeof(ExpanderRowHeightBehavior),
                    new PropertyMetadata(null));
    
            public static RowDefinition GetTargetRow(DependencyObject obj)
            {
                return (RowDefinition)obj.GetValue(TargetRowProperty);
            }
    
            public static void SetTargetRow(DependencyObject obj, RowDefinition value)
            {
                obj.SetValue(TargetRowProperty, value);
            }
            #endregion
    
            #region TargetRowPrevHeight (Attached Property)
            public static readonly DependencyProperty TargetRowPrevHeightProperty =
                DependencyProperty.RegisterAttached(
                    "TargetRowPrevHeight",
                    typeof(GridLength),
                    typeof(ExpanderRowHeightBehavior),
                    new PropertyMetadata(GridLength.Auto));
    
            public static GridLength GetTargetRowPrevHeight(DependencyObject obj)
            {
                return (GridLength)obj.GetValue(TargetRowPrevHeightProperty);
            }
    
            public static void SetTargetRowPrevHeight(DependencyObject obj, GridLength value)
            {
                obj.SetValue(TargetRowPrevHeightProperty, value);
            }
            #endregion
    
            private static void OnCollapsed(object sender, RoutedEventArgs e)
            {
                if (!(sender is Expander expander)) return;
    
                var targetRow = GetTargetRow(expander);
    
                if (targetRow == null) return;
    
                SetTargetRowPrevHeight(expander, targetRow.Height);
    
                targetRow.Height = GridLength.Auto;
            }
    
            private static void OnExpanded(object sender, RoutedEventArgs e)
            {
                if (!(sender is Expander expander)) return;
    
                var targetRow = GetTargetRow(expander);
    
                if (targetRow == null) return;
    
                var targetRowPrevHeight = GetTargetRowPrevHeight(expander);
    
                targetRow.Height = targetRowPrevHeight;
            }
        }
    }