如何有网格行高度只拿它需要的空间,而且在其容器可用空间受到限制?(How to have Grid

2019-10-28 13:26发布

I want to have a header, then below that a ScrollViewer with an ItemsControl, then below that a footer. Something like:

<Window x:Class="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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid ShowGridLines="True">
        <Grid.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="FontSize" Value="36"/>
            </Style>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0">Header</TextBlock>
        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
            <ItemsControl>
                <ItemsControl.Items>
                    <TextBlock>Item 1</TextBlock>
                    <TextBlock>Item 2</TextBlock>
                    <TextBlock>Item 3</TextBlock>
                    <TextBlock>Item 4</TextBlock>
                    <TextBlock>Item 5</TextBlock>
                </ItemsControl.Items>
            </ItemsControl>
        </ScrollViewer>
        <TextBlock Grid.Row="2">Footer</TextBlock>
    </Grid>
</Window>

The above is almost what I want, except the middle row is greedy; even if the window is very tall, it takes up as much space as it can, pushing the footer to the bottom of the window.

If I change the middle row's definition to Height="Auto", it takes up exactly the amount of space that it needs, even if that space isn't available, so the ScrollViewer never shows the scrollbar, and the footer will get lost off the bottom of the window if the window isn't tall enough.

How do I make it so that if the window is tall enough for everything to fit, the footer is immediately below the ItemsControl, but if the window isn't tall enough, the ScrollViewer shows a scrollbar and the footer is at the bottom of the window?

I don't necessarily need to do this with a Grid, but I didn't find any other Panel that would do what I want either. E.g., a DockPanel with the header set to DockPanel.Dock="Top", the footer set to DockPanel.Dock="Bottom", and the ItemsControl filling the rest behaves exactly the same way.

Some other stuff I've tried:

  • Setting VerticalAlignment="Stretch" on the footer TextBlock: no change.
  • Making the footer row Height="*": still not what I want; the footer and the ItemsControl get the same height, so the footer takes up too much space most of the time, or if you make the window extremely short, it goes off the bottom of the window.

Answer 1:

有可能是一个更优雅的方式,但至少下面的工作对我来说在类似的情况。 它是关于计算ItemsControl中的所有项目的实际heigth,如果需要调整网格的行高。

<Grid ShowGridLines="True" Loaded="Grid_Loaded">
    <Grid.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="36"/>
        </Style>
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*" Name="middlerow"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0">Header</TextBlock>
    <ScrollViewer Name="scv" Grid.Row="1" VerticalScrollBarVisibility="Auto">
        <ItemsControl x:Name="items" Height="Auto" VerticalAlignment="Top">
            <ItemsControl.Items>
                <TextBlock>Item 1</TextBlock>
                <TextBlock>Item 2</TextBlock>
                <TextBlock>Item 3</TextBlock>
                <TextBlock>Item 4</TextBlock>
            </ItemsControl.Items>
        </ItemsControl>
    </ScrollViewer>
    <TextBlock Grid.Row="2">Footer</TextBlock>
</Grid>

而在后面的代码:

private void Grid_Loaded(object sender, RoutedEventArgs e)
    {
        double height = 0;
        foreach (var item in items.Items)
        {
            height += (item as TextBlock).ActualHeight;
        }
        if (height < scv.ActualHeight)
        {
            middlerow.MaxHeight = height;
        }
    }


Answer 2:

由于真身的回答对于如何做到这一点的概念, 马库斯的回答为理念,结合该行的MaxHeight

XAML:

<Window x:Class="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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid ShowGridLines="True">
        <Grid.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="FontSize" Value="36"/>
            </Style>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" MaxHeight="{Binding ItemsMaxHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0">Header</TextBlock>
        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
            <ItemsControl Name="ic" SizeChanged="Ic_SizeChanged">
                <ItemsControl.Items>
                    <TextBlock>Item 1</TextBlock>
                    <TextBlock>Item 2</TextBlock>
                    <TextBlock>Item 3</TextBlock>
                    <TextBlock>Item 4</TextBlock>
                    <TextBlock>Item 5</TextBlock>
                </ItemsControl.Items>
            </ItemsControl>
        </ScrollViewer>
        <TextBlock Grid.Row="2">Footer</TextBlock>
    </Grid>
</Window>

后面的代码:

Imports System.ComponentModel

Class MainWindow
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public ReadOnly Property ItemsMaxHeight As Double
        Get
            Dim Height = 0.0
            Dim icg = ic.ItemContainerGenerator

            For i = 0 To ic.Items.Count - 1
                Height += DirectCast(icg.ContainerFromIndex(i), FrameworkElement).ActualHeight
            Next

            Return Height + 6.0 ' 6.0 to account for the size of borders? Not sure :(
        End Get
    End Property

    Private Sub Ic_SizeChanged(sender As Object, e As SizeChangedEventArgs)
        If e.HeightChanged Then
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ItemsMaxHeight"))
        End If
    End Sub
End Class

在此示例XAML,我不需要以6比增加MaxHeight,但是在我的实际应用程序,如果我没有在一点点额外的添加,在ScrollViewer总是显示可以滚动一点点滚动条。 不知道是什么原因导致的差异。



文章来源: How to have Grid row height take only the space it needs, but also be constrained by available space in its container?
标签: wpf layout