WPF: collapse GridSplitter? WPF: collapse GridSplitter? wpf wpf

WPF: collapse GridSplitter?


In case anyone wants a purely XAML solution, I was able to conjure a way to hide the splitter and relevant row using styles, setters, and triggers.

I used a static resource for my style, which was set to change the Height and MaxHeight when a specific bound boolean was set.

<Style x:Key="showRow" TargetType="{x:Type RowDefinition}">  <Style.Setters>    <Setter Property="Height" Value="*"/>  </Style.Setters>  <Style.Triggers>    <DataTrigger Binding="{Binding MyShowRowBool}" Value="False">      <DataTrigger.Setters>        <Setter Property="Height" Value="0"/>        <Setter Property="MaxHeight" Value="0"/>      </DataTrigger.Setters>    </DataTrigger>  </Style.Triggers></Style>

I simply applied the style to the relevant row definitions, and it worked like a charm:

<Grid.RowDefinitions>  <RowDefinition Height="*"/>  <RowDefinition Style="{StaticResource showRow}"/>  <RowDefinition Style="{StaticResource showRow}"/></Grid.RowDefinitions>

Notable is that I tried it without the MaxHeight property, and it wasn't collapsing properly. Adding it in seems to have done the trick for me.


Let's suppose I have this XAML layout:

  <Grid Name="MyGrid">      <Grid.RowDefinitions>          <RowDefinition />          <RowDefinition Height="Auto" />          <RowDefinition />      </Grid.RowDefinitions>      <MyControl1 ... Grid.Row="0" />      <GridSplitter Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" ShowsPreview="True" Height="5" />      <MyControl2 ... Grid.Row="2" />  </Grid>

Then I can hide the second control (collapse the splitter down) with this code (equivalent of setting Height="0" in XAML):

  MyGrid.RowDefinitions[2].Height = new GridLength(0);

And uncollapse it with this code (equivalent of setting Height="1*" in XAML, which is the default for a RowDefinition):

  MyGrid.RowDefinitions[2].Height = new GridLength(1, GridUnitType.Star);

This is what the splitter does undercovers when the user moves it.


I had to introduce an Attached Dependency Property to handle this in my own application:

<Grid c:GridSplitterController.Watch="{Binding ElementName=GS_DetailsView}">    <Grid.RowDefinitions>        <RowDefinition Height="1*" />        <RowDefinition Height="200" />    </Grid.RowDefinitions>    <SomeControl Grid.Row="0" />    <GridSplitter x:Name="GS_DetailsView"                  Height="4"                  Grid.Row="1"                  VerticalAlignment="Top"                  HorizontalAlignment="Stretch"                  ResizeBehavior="PreviousAndCurrent"                  ResizeDirection="Rows"                  Visibility="{Binding ShowDetails,                                       Converter={StaticResource boolvis}}" />    <OtherControl Grid.Row="1"                  Margin="0,4,0,0"                  Visibility="{Binding ShowDetails,                                       Converter={StaticResource boolvis}}" /></Grid>

First define a suitable attached property on a DependencyObject:

public static GridSplitter GetWatch(DependencyObject obj){    return (GridSplitter)obj.GetValue(WatchProperty);}public static void SetWatch(DependencyObject obj, GridSplitter value){    obj.SetValue(WatchProperty, value);}public static readonly DependencyProperty WatchProperty =    DependencyProperty.RegisterAttached(        "Watch",        typeof(GridSplitter),        typeof(DependencyObject),        new UIPropertyMetadata(null, OnWatchChanged));

Then listen for IsVisibleChanged:

private static void OnWatchChanged(DependencyObject obj,    DependencyPropertyChangedEventArgs e){    if (obj == null) return;    if (obj is Grid)    {        var grid = obj as Grid;        var gs = e.NewValue as GridSplitter;        if (gs != null)        {            gs.IsVisibleChanged += (_sender, _e) =>                {                    UpdateGrid(                        grid,                        (GridSplitter)_sender,                        (bool)_e.NewValue,                        (bool)_e.OldValue);                };        }    }}

Once you are watching for these changes, you need to save or restore the GridLength values from the row or columns you are watching (for brevity I'm only including Rows):

// Given: static Dictionary<DependencyObject, GridLength> oldValues;private static void UpdateGrid(Grid grid, GridSplitter gridSplitter, bool newValue, bool oldValue){    if (newValue)    {        // We're visible again        switch (gridSplitter.ResizeDirection)        {        case GridResizeDirection.Columns:            break;        case GridResizeDirection.Rows:            int ridx = (int)gridSplitter.GetValue(Grid.RowProperty);            var prev = grid.RowDefinitions.ElementAt(GetPrevious(gridSplitter, ridx));            var curr = grid.RowDefinitions.ElementAt(GetNext(gridSplitter, ridx));            if (oldValues.ContainsKey(prev) && oldValues.ContainsKey(curr))            {                prev.Height = oldValues[prev];                curr.Height = oldValues[curr];            }            break;        }    }    else    {        // We're being hidden        switch (gridSplitter.ResizeDirection)        {        case GridResizeDirection.Columns:            break;        case GridResizeDirection.Rows:            int ridx = (int)gridSplitter.GetValue(Grid.RowProperty);            var prev = grid.RowDefinitions.ElementAt(GetPrevious(gridSplitter, ridx));            var curr = grid.RowDefinitions.ElementAt(GetNext(gridSplitter, ridx));            switch (gridSplitter.ResizeBehavior)            {                // Naively assumes only one type of collapsing!                case GridResizeBehavior.PreviousAndCurrent:                    oldValues[prev] = prev.Height;                    prev.Height = new GridLength(1.0, GridUnitType.Star);                    oldValues[curr] = curr.Height;                    curr.Height = new GridLength(0.0);                    break;            }            break;        }    }}

All that is left is a suitable implementation of GetPrevious and GetNext:

private static int GetPrevious(GridSplitter gridSplitter, int index){    switch (gridSplitter.ResizeBehavior)    {        case GridResizeBehavior.PreviousAndNext:        case GridResizeBehavior.PreviousAndCurrent:            return index - 1;        case GridResizeBehavior.CurrentAndNext:            return index;        case GridResizeBehavior.BasedOnAlignment:        default:            throw new NotSupportedException();    }}private static int GetNext(GridSplitter gridSplitter, int index){    switch (gridSplitter.ResizeBehavior)    {        case GridResizeBehavior.PreviousAndCurrent:            return index;        case GridResizeBehavior.PreviousAndNext:        case GridResizeBehavior.CurrentAndNext:            return index + 1;        case GridResizeBehavior.BasedOnAlignment:        default:            throw new NotSupportedException();    }}