WPF Grid - Auto-sizing with minimum height constra

2019-07-08 02:08发布

问题:

Background

I need a grid with the following layout properties:

  1. 4 rows: header, main content, sub-content, footer
  2. The header is static content which isn't really affected by resize
  3. The main content needs to fill up all available space, with a minimum height of 180
  4. The sub-content is a RTB that can shrink and grow. This sub-content can eat up some of the main-contents space, but should always leave 180 pixels for the main-content. The sub-content should ideally only take up the minimum amount of area it needs. If there isn't much room left in the grid, the RTB should stop growing and instead enable its internal scrollviewer.
  5. The footer is like the header, static content unaffected by resize

The problem

The sub-content (RTB) isn't auto-sizing itself to fit within the remaining space, nor is the vertical scroll-bar being enabled. This is causing anything below the main content to clip outside of the window.

Question

How can I get the RichTextBox to shrink so that the footer is shown, allowing the user to scroll through the hidden RichTextBox content, while simultaneously allowing the RichTextBox to expand if the user stretches the window?

Below you will find a SSCCE which demonstrates what I'm trying to achieve and the issues that it's causing:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        MinWidth="200" MinHeight="300" Width="200" Height="300">

    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" MinHeight="180"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="Header" HorizontalAlignment="Center"/>

        <Rectangle Grid.Row="1" Fill="Red"/>

        <RichTextBox Grid.Row="2"
                     VerticalScrollBarVisibility="Visible"
                     Height="Auto"
                     Margin="0,5,0,0"
                     VerticalAlignment="Stretch"
                     BorderBrush="#FF818181"
                     BorderThickness="0.5"
                     Background="#FFEEEEEE"
                     FontSize="14">
            <FlowDocument>
                <List>
                    <ListItem>
                        <Paragraph>Lorem</Paragraph>
                        <Paragraph>IpSum</Paragraph>
                        <Paragraph>Lorem</Paragraph>
                        <Paragraph>IpSum</Paragraph>
                    </ListItem>
                </List>
            </FlowDocument>
        </RichTextBox>

        <TextBlock Grid.Row="3" Text="Footer" HorizontalAlignment="Center"/>

    </Grid>
</Window>

Here is an image of the window when it's at its minimum size:

Here is an image of the window when it has been stretched to show all:

Extra information

I know that if I set the sub-content RowDefinition to * then the RichTextBox works fine, except for the fact it takes too much room when the window is expanded. I need this area to take as much room as Auto while behaving like *.

回答1:

Divide and conquer

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication2"
        MinWidth="200" MinHeight="300" Width="200" Height="300">
    <Window.Resources>
        <local:HeightConverter x:Key="HeightConverter" />
    </Window.Resources>

    <!--MainGrid-->
    <Grid Name="grid">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

    <!--AlignmentGrid-->
        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*" MinHeight="180"/>
                <RowDefinition Height="Auto">
                </RowDefinition>
            </Grid.RowDefinitions>
            <TextBlock Name="head" Grid.Row="0" Text="Header" HorizontalAlignment="Center"/>
            <Rectangle Name="rect" Grid.Row="1" Fill="Red"/>
            <RichTextBox Grid.Row="2"
                         VerticalScrollBarVisibility="Visible"
                         Margin="0,5,0,0"
                         VerticalAlignment="Stretch"
                         BorderBrush="#FF818181"
                         BorderThickness="0.5"
                         Background="#FFEEEEEE"
                         FontSize="14">
                <RichTextBox.MaxHeight>
                    <MultiBinding Converter="{StaticResource HeightConverter}">
                        <Binding ElementName="grid" Path="ActualHeight"/>
                        <Binding ElementName="head" Path="ActualHeight"/>
                        <Binding ElementName="rect" Path="ActualHeight"/>
                        <Binding ElementName="foot" Path="ActualHeight"/>
                    </MultiBinding>
                </RichTextBox.MaxHeight>
                <FlowDocument>
                    <List>
                        <ListItem>
                            <Paragraph>Lorem</Paragraph>
                            <Paragraph>IpSum</Paragraph>
                            <Paragraph>Lorem</Paragraph>
                            <Paragraph>IpSum</Paragraph>
                        </ListItem>
                    </List>
                </FlowDocument>
            </RichTextBox>
        </Grid>

    <!--Footer-->
        <TextBlock Name="foot" Grid.Row="1" Text="Footer" HorizontalAlignment="Center"/>
    </Grid>
</Window>

The Converter:

public class HeightConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double gridHeight = (double)values[0];
        double headHeight = (double)values[1];
        double rectHeight = (double)values[2];
        double footHeight = (double)values[3];

        return gridHeight - headHeight - rectHeight - footHeight;
    }

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

Note that for the Converter to give the desired result, the height of the MainGrid may not exceed the height of the content in the Window.