3
votes

My customer developing a property editor of sorts that contains multiple property sections that are presented as WPF Expanders with Datagrids contained within. We have everything laid out in a WPF Grid, currently with auto sized rows.

The problem we're trying to solve is proportional sizing of each property section when the screen height changes. When expanders are opened they should take up a proportional amount of space with vertical scrollbars kicking automatically when needed. When they are closed they should collapse and give the remaining space to the remaining sections.

In real life we have 4 expanders with content. The first one is a fixed height, the remaining three are Lists and DataGrids that need to size proportionally with the last one getting the largest portion of the remaining space. Keep in mind, users might resize the screen, or users might be running in different screen resolutions, so we need it to react accordingly.

We've tried messing with the DataGrid RowDefinition Height (fixed, proportional and auto) and MaxHeight, and also the MaxHeight of each datagrid, but I can't seem to find a combination that causes the whole area to be consumed when needed. We're researching a code based solution, but we're curious what others may suggest.

Here's a simple code sample that will give you the layout we're after. We just need it to work as described above.

Here's The Code (this would be a view model in real world)

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        datagrid1.DataContext = GetCustomerVM();
        datagrid2.DataContext = GetCustomerVM();
        datagrid3.DataContext = GetCustomerVM();
    }

    private List<Customer> GetCustomerVM()
    {
        var CustomerList = new List<Customer>();
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });
        CustomerList.Add(new Customer() { FirstName = "A" });

        return CustomerList;
    }
}

public class Customer
{
    public string FirstName { get; set; }
}

The XAML

<Window x:Class="StackOverflow_SidePanelGridScrolling.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:StackOverflow_SidePanelGridScrolling"
        mc:Ignorable="d"
        Loaded="Window_Loaded"
        Title="MainWindow" Height="500" Width="300">

    <Window.Resources>

        <DataTemplate x:Key="ExpanderHeaderTemplate">
            <DockPanel Height="30">
                <TextBlock Margin="4,0,0,0"
                               VerticalAlignment="Center"
                               DockPanel.Dock="Left"
                               FontSize="16"
                               Text="{Binding}" />

            </DockPanel>
        </DataTemplate>

        <Style TargetType="Expander">
            <Setter Property="HeaderTemplate" 
                    Value="{StaticResource ExpanderHeaderTemplate}" />
        </Style>

    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Expander x:Name="expander1" Grid.Row="0" Header="Area 1">

            <DataGrid Name="datagrid1" 
                      ItemsSource="{Binding}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="First Name" 
                                        Binding="{Binding FirstName}"
                                        Width="100*" />
                </DataGrid.Columns>
            </DataGrid>

        </Expander>

        <Expander x:Name="expander2" Grid.Row="1" Header="Area 2">

            <DataGrid Name="datagrid2" 
                      ItemsSource="{Binding}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="First Name" 
                                        Binding="{Binding FirstName}"
                                        Width="100*" />
                </DataGrid.Columns>
            </DataGrid>

        </Expander>

        <Expander x:Name="expander3" Grid.Row="3" Header="Area 3">

            <DataGrid Name="datagrid2" 
                      ItemsSource="{Binding}" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="First Name" 
                                        Binding="{Binding FirstName}"
                                        Width="100*" />
                </DataGrid.Columns>
            </DataGrid>

        </Expander>

    </Grid>
</Window>
1

1 Answers

2
votes

As far as I'm able to understand your requirements, I think this does about what you want:

  • Every expander, when not expanded, takes up whatever minimal space it requires.
  • The top row, when expanded, is sized to its content.
  • Rows two, three, and four divide up the remainder of the space. When expanded, they stretch. Two gets one share of the available space, Three gets two shares, Four gets three shares. If Two and Three are open, Two gets one of four shares and Three gets the other three.

Those shares are handed out by the * Height values in the setters in the triggers. Here's the trigger for Row Four:

<DataTrigger
    Binding="{Binding Children[3].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
    Value="True"
    >
    <Setter Property="Height" Value="3*" />
</DataTrigger>

The nice thing about doing this in XAML is that it will never spring any ugly surprises on you.

So here's the XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition>
            <RowDefinition.Style>
                <Style TargetType="RowDefinition">
                    <Setter Property="Height" Value="Auto" />
                    <Style.Triggers>
                        <DataTrigger
                            Binding="{Binding Children[1].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
                            Value="True"
                            >
                            <Setter Property="Height" Value="1*" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </RowDefinition.Style>
        </RowDefinition>
        <RowDefinition>
            <RowDefinition.Style>
                <Style TargetType="RowDefinition">
                    <Setter Property="Height" Value="Auto" />
                    <Style.Triggers>
                        <DataTrigger
                            Binding="{Binding Children[2].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
                            Value="True"
                            >
                            <Setter Property="Height" Value="2*" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </RowDefinition.Style>
        </RowDefinition>
        <RowDefinition>
            <RowDefinition.Style>
                <Style TargetType="RowDefinition">
                    <Setter Property="Height" Value="Auto" />
                    <Style.Triggers>
                        <DataTrigger
                            Binding="{Binding Children[3].IsExpanded, RelativeSource={RelativeSource AncestorType=Grid}}"
                            Value="True"
                            >
                            <Setter Property="Height" Value="3*" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </RowDefinition.Style>
        </RowDefinition>
    </Grid.RowDefinitions>
    <Expander
        Grid.Row="0"
        VerticalAlignment="Stretch"
        Header="One" Background="LightGreen">
        <StackPanel>
            <Label>Content One</Label>
            <Label>Content One</Label>
            <Label>Content One</Label>
        </StackPanel>
    </Expander>
    <Expander
        Grid.Row="1"
        VerticalAlignment="Stretch"
        Header="Two" Background="LightSkyBlue">
        <StackPanel>
            <Label>Content Two</Label>
            <Label>Content Two</Label>
            <Label>Content Two</Label>
        </StackPanel>
    </Expander>
    <Expander
        Grid.Row="2"
        VerticalAlignment="Stretch"
        Header="Three" Background="LightGoldenrodYellow">
        <StackPanel>
            <Label>Content Three</Label>
            <Label>Content Three</Label>
            <Label>Content Three</Label>
        </StackPanel>
    </Expander>
    <Expander
        Grid.Row="3"
        VerticalAlignment="Stretch"
        Header="Four" Background="Khaki">
        <StackPanel>
            <Label>Content Four</Label>
            <Label>Content Four</Label>
            <Label>Content Four</Label>
        </StackPanel>
    </Expander>
</Grid>

These don't show scrollbars because I didn't take the time to create content that will scroll.

enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here