Search code examples
wpfxamlgridsplitter

GridSplitter with min constraints


I want a Grid layout with two rows and splitter between them. Rows should have a minimum height of 80 pixels.

This code works great:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" MinHeight="80" />
        <RowDefinition Height="5" />
        <RowDefinition Height="*" MinHeight="80" />
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Text="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}" />
    <GridSplitter Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Red" />
    <TextBlock Grid.Row="2" Text="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}" />
</Grid>

But I want top row to have an Auto height until user manually change it using the splitter. So I changed the code to this:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" MinHeight="80" />
        <RowDefinition Height="5" />
        <RowDefinition Height="*" MinHeight="80" />
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Text="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}" />
    <GridSplitter Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Red" />
    <TextBlock Grid.Row="2" Text="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}" />
</Grid>

And there is a problem. Splitter still satisfies row constraints, but it begins to increase top row's height infinitely if I drag splitter too low. This results in bottom row to be completely below window's bottom border.

I have done some Reflector on GridSplitter code and see that it uses different logic if rows has Auto or Star height.

Any suggestions how can I "fix" it?


Solution

  • I have developed a workaround for this problem. Point is to set MaxHeight for the top row while we are dragging splitter. Here the code:

    public class FixedGridSplitter : GridSplitter
    {
        private Grid grid;
        private RowDefinition definition1;
        private double savedMaxLength;
    
        #region static
    
        static FixedGridSplitter()
        {
            new GridSplitter();
            EventManager.RegisterClassHandler(typeof(FixedGridSplitter), Thumb.DragCompletedEvent, new DragCompletedEventHandler(FixedGridSplitter.OnDragCompleted));
            EventManager.RegisterClassHandler(typeof(FixedGridSplitter), Thumb.DragStartedEvent, new DragStartedEventHandler(FixedGridSplitter.OnDragStarted));
        }
    
        private static void OnDragStarted(object sender, DragStartedEventArgs e)
        {
            FixedGridSplitter splitter = (FixedGridSplitter)sender;
            splitter.OnDragStarted(e);
        }
    
        private static void OnDragCompleted(object sender, DragCompletedEventArgs e)
        {
            FixedGridSplitter splitter = (FixedGridSplitter)sender;
            splitter.OnDragCompleted(e);
        }
    
        #endregion
    
        private void OnDragStarted(DragStartedEventArgs sender)
        {            
            grid = Parent as Grid;
            if (grid == null)
                return;            
            int splitterIndex = (int)GetValue(Grid.RowProperty);
            definition1 = grid.RowDefinitions[splitterIndex - 1];
            RowDefinition definition2 = grid.RowDefinitions[splitterIndex + 1];
            savedMaxLength = definition1.MaxHeight;            
    
            double maxHeight = definition1.ActualHeight + definition2.ActualHeight - definition2.MinHeight;            
            definition1.MaxHeight = maxHeight;
        }
    
        private void OnDragCompleted(DragCompletedEventArgs sender)
        {
            definition1.MaxHeight = savedMaxLength;
            grid = null;
            definition1 = null;
        }
    }
    

    Then just replace GridSplitter with FixedGridSplitter.

    Note: this code is not general - it doesn't support columns and assume that bottom row has MinHeight specified.