Carl Quirion
Carl Quirion

Reputation: 815

In WPF How to prevent Controls inside ScrollViewer from expanding

I'm trying to achieve something that sounds pretty simple, in WPF, but just can't get around doing it. I have a ScrollViewer which contains two GroupBoxes. First one has it's height set to a fixed value, second one would take what's left of the window but have a MinHeight. Each GroupBox contains a DataGrid.

What i'm trying to do is : The second groupbox should be sized to what's left of the Window and the DataGrid inside of it should be sized to fill the group box and have it's own scrollbar if rows can't all be shown. A scrollbar should appear in the window if i resize the window to be less than GroupBox1.Height+GroupBox2.MinHeight.

The behavior i get now is, the DataGrid in the second groupbox's height grows with the number of lines, thus expanding the Groupbox and having the Scrollviewer's scrollbar show up.

I came up with a little demo-app to show this behavior

WPF:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow"
    Height="400"
    Width="500">
<Grid>
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="150" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <GroupBox Header="test1"
                      Grid.Row="0">
                <DataGrid ItemsSource="{Binding Colors}">
                </DataGrid>
            </GroupBox>
            <GroupBox Header="test2"
                      Grid.Row="1"
                      MinHeight="50">
                <DataGrid ItemsSource="{Binding Colors}">
                </DataGrid>
            </GroupBox>
        </Grid>

    </ScrollViewer>
</Grid>

C#

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
            Colors = new List<Color>();
            for (int i = 1; i < 51; i++)
            {
                byte b = (byte)(i * 5);
                Colors.Add(Color.FromRgb(b,b,b));
            }
        }

        private List<Color> _colors;
        public List<Color> Colors
        {
            get
            {
                return _colors;
            }
            set
            {
                _colors = value;
            }
        }
    }
}

What i'm getting :

What i'm getting

What i would want (sorry for the bad photo-manipulation skills) :

What i would want

Unless, as specified earlier, i resize the window to be smaller than the sum of the group1's fixed size and group2's min size, in which case i want the window's scrollbar.

In which case, i would want it to look like this : (again a mock-up, not an actual screenshot)

Mockup2

Mind you, the example is pretty simple but the window i'm trying to do it in is much more complex and it makes more sense to have a vertical scrollbar than it does in this example.

Thanks!

Upvotes: 3

Views: 2834

Answers (2)

You can simply bind the MaxHeight property of the second GroupBox to the ActualHeight of the container of the ScrollViewer minus the first GroupBox.

Complete example (excluding the code behind which is same as yours.):

<Window.Resources>
    <wpfApp1:SubtractConverter x:Key="SubtractConverter"/>
</Window.Resources>

<Grid Name="Root">
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="150" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <GroupBox
                Name="Test1"
                Header="test1"
                Grid.Row="0">

                <DataGrid ItemsSource="{Binding Colors}"/>
            </GroupBox>
            <GroupBox
                Header="test2"
                Grid.Row="1"
                MinHeight="250">
                <DataGrid ItemsSource="{Binding Colors}"/>

                <GroupBox.MaxHeight>
                    <MultiBinding Converter="{StaticResource SubtractConverter}">
                        <Binding Path="ActualHeight" ElementName="Root"/>
                        <Binding Path="ActualHeight" ElementName="Test1"/>
                    </MultiBinding>
                </GroupBox.MaxHeight>
            </GroupBox>
        </Grid>
    </ScrollViewer>
</Grid>

public class SubtractConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double[] doubles = values.Cast<double>().ToArray();

        double result = doubles[0];

        for (int i = 1; i < doubles.Length; i++)
        {
            result -= doubles[i];
        }

        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Upvotes: 5

MarianM
MarianM

Reputation: 91

I don't know if this would be the easiest solution for your problem but you could do something along this line:

<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Height="400"
Width="500">
<Window.Resources>
    <local:HeightConverter x:Key="HeightConverter"/>
</Window.Resources>
<Grid>
    <ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="MainView">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="150" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <GroupBox Header="test1"
                  Grid.Row="0">
                <DataGrid ItemsSource="{Binding Colors}">
                </DataGrid>
            </GroupBox>
            <GroupBox Header="test2"
                  Grid.Row="1"
                  x:Name="grpBox2"
                  MinHeight="50">
                <GroupBox.Height>
                    <MultiBinding Converter="{StaticResource HeightConverter}" ConverterParameter="150">
                        <Binding Path="ActualHeight" ElementName="MainView" />
                        <Binding Path="MinHeight" ElementName="grpBox2" />
                    </MultiBinding>
                </GroupBox.Height>
                <DataGrid ItemsSource="{Binding Colors}">
                </DataGrid>
            </GroupBox>
        </Grid>

    </ScrollViewer>
</Grid>

And for the converter something like this:

public class HeightConverter : IMultiValueConverter
{
   public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
   {
        if (values == null || parameter == null || values[0] == null || values[1] == null)
        {
            return null;
        }

        var currentWindowHeight = double.Parse(values[0].ToString());
        var currentMinHeight = double.Parse(values[1].ToString());

        var currentTopWindowHeight = double.Parse(parameter.ToString());

        var newHeight = currentWindowHeight - currentTopWindowHeight;

        if (newHeight < currentMinHeight)
            newHeight = currentMinHeight;

        return newHeight;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Upvotes: 2

Related Questions