Search code examples
c#wpfxamlgridgridsplitter

GridSplitter Autosize


I got a problem with the GridSplitter

Simple example code:

<Grid x:Name="MainGrid">
    <Grid.RowDefinitions>
        <RowDefinition Height="100"></RowDefinition>
        <RowDefinition Height="2"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid x:Name="TopGrid" Grid.Row="0"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Red"/>
    <GridSplitter ResizeDirection="Rows" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Height="2" Background="Black" />
    <Grid x:Name="BottomGrid" Grid.Row="2" HorizontalAlignment="Stretch"  Background="Aquamarine" VerticalAlignment="Stretch"/>
</Grid>

This creates two Grids vertically seperated by a GridSplitter.

What I want to achieve is, that the GridSplitter automaticly align to the Grid´s content. For example if I got a collapsable element in the bottom Grid, I want the top Grid to become bigger if I collapse the element. If I expand it, the top Grid should become smaller.

How do I do this? Later I will have 4 Grids with 3 GridSplitter´s... so the solution should work with multiple GridSplitter´s as well.

[edit]:

my xaml:

 <Grid x:Name="MainGrid">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="100">
            <RowDefinition.MinHeight>
                <MultiBinding Converter="{StaticResource ResourceKey=MultiValueConverter}">
                    <Binding ElementName="dgStapelliste" Path="ActualHeight"></Binding>
                </MultiBinding>
            </RowDefinition.MinHeight>
        </RowDefinition>
        <RowDefinition Height="1"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition >           
    </Grid.RowDefinitions>

    <Grid Grid.Row="0" Grid.Column="0" x:Name="Test">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <WPF:DataGrid GridHeaderContent="{Binding StapelListe.HeaderText}" SelectedItem="{Binding StapelListe.SelectedItem}" Grid.Row="0" Grid.Column="0" x:Name="dgStapelliste" HorizontalAlignment="Stretch" ItemsSource="{Binding StapelListe, Mode=OneWay,  UpdateSourceTrigger=PropertyChanged}"/>
        <WPF:DataGrid GridHeaderContent="{Binding EinzelListe.HeaderText}" Grid.Row="0" Grid.Column="1" x:Name="dgEinzelliste" HorizontalAlignment="Stretch"  ItemsSource="{Binding EinzelListe, Mode=OneWay}"/>

        <GridSplitter Grid.Column="0" Width="1" VerticalAlignment="Stretch" Background="Black" />
    </Grid>

    <Grid Grid.Row="2" Grid.Column="0" Grid.RowSpan="2">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>

        <WPF:DataGrid  GridHeaderContent="{Binding Anforderungsliste.HeaderText}" Grid.Column="0" x:Name="dgAnforderungsliste" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Anforderungsliste, Mode=OneWay}"/>

        <GridSplitter Grid.Column="0" Width="1" VerticalAlignment="Stretch" Background="Black" />
    </Grid>
    <GridSplitter Grid.Column="0" Grid.Row="1" Height="1" ResizeDirection="Rows"  VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Black" x:Name="testSplitter" />
</Grid>

Solution

  • You could use MultiBinding to achieve what you want.

    For each RowDefinition you set a MinHeight which is bound to its contents ActualHeight's like so:

    <RowDefinition Height="100">
        <RowDefinition.MinHeight>
            <MultiBinding Converter="{StaticResource ResourceKey=CalcAll}">
                <Binding ElementName="firstElementInThisRow" Path="ActualHeight"></Binding>
                <Binding ElementName="secondElementInThisRow" Path="ActualHeight"></Binding>
                <Binding ElementName="thirdElementInThisRow" Path="ActualHeight"></Binding>
                <Binding ElementName="fourthElementInThisRow" Path="ActualHeight"></Binding>
            </MultiBinding>
        </RowDefinition.MinHeight>
    </RowDefinition>
    

    Your Converter could look like:

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double result = 0.0;
        foreach (object item in values)
        {
            result += System.Convert.ToDouble(item);
        }
        return result;
    }
    
    public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
    

    Everytime you expand a control, its ActualHeight changes and the Binding gets update -> MinHeight of the parents RowDefinition changes.

    But you cant set one if the controls VerticalAlignment to Stretch, because then the ActualHeight wouldn't change by expanding.

    EDIT: Since the only property I can now think of is the DesiredSize.Height-property, you can't use Binding (the binding won't update, if the DesiredSize.Height-value changes). But perhaps you can use a property (let's call it MinHeightRowOne) of type double which raises the PropertyChanged event in it's setter and is bound to the first rows MinHeight (one property for each row):

    public double _minHeightRowOne;
    public double MinHeightRowOne
    {
        get
        {
            return _minHeightRowOne;
        }
        set
        {
            _minHeightRowOne = value;
            OnPropertyChanged("MinHeightRowOne");
        }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
    


    <RowDefinition Height="100" MinHeight="{Binding Path=MinHeightRowOne}"/>
    

    Now add this EventHandler to the SizeChanged-Event of every control in the first row (one handler for each row):

    private List<KeyValuePair<string,double>> oldVals = new List<KeyValuePair<string,double>>();
    
    private void ElementInRowOneSizeChanged(object sender, SizeChangedEventArgs e)
    {
        FrameworkElement elem = (FrameworkElement)sender;
        MinHeightRowOne -= oldVals.Find(kvp => kvp.Key == elem.Name).Value;
        elem.Measure(new Size(int.MaxValue, int.MaxValue));
        MinHeightRowOne += elem.DesiredSize.Height;
        oldVals.Remove(oldVals.Find(kvp => kvp.Key == elem.Name));
        oldVals.Add(new KeyValuePair<string, double>(elem.Name, elem.DesiredSize.Height));
    }
    

    Through this the MinHeight of the Rows get updated everytime the Size of a control changes (which should include expanding or collapsing items).

    Note that every control must have an unique name to make this work.