【问题标题】:How to have Grid row height take only the space it needs, but also be constrained by available space in its container?如何让 Grid 行高只占用它需要的空间,但又受其容器中可用空间的限制?
【发布时间】:2019-06-15 12:39:03
【问题描述】:

我想要一个页眉,然后在下面有一个ScrollViewer 和一个ItemsControl,然后在下面有一个页脚。比如:

<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>

上面的差不多就是我想要的了,除了中间那一排是贪心的;即使窗口很高,它也会尽可能多地占用空间,将页脚推到窗口底部。

如果我将中间行的定义更改为Height="Auto",它将完全占用所需的空间量,即使该空间不可用,所以ScrollViewer 永远不会显示滚动条,而页脚将如果窗户不够高,就会从窗户底部迷路。

我如何做到这一点,如果窗口足够高以容纳所有内容,页脚就在 ItemsControl 的下方,但如果窗口不够高,ScrollViewer 会显示一个滚动条,并且页脚位于窗口底部?

我不一定需要使用Grid 执行此操作,但我没有找到任何其他Panel 可以满足我的要求。例如,DockPanel 的标头设置为 DockPanel.Dock="Top",页脚设置为 DockPanel.Dock="Bottom",而填充其余部分的 ItemsControl 的行为方式完全相同。

我尝试过的其他一些东西:

  • 在页脚TextBlock 上设置VerticalAlignment="Stretch":没有变化。
  • 制作页脚行Height="*":仍然不是我想要的;页脚和ItemsControl 的高度相同,因此页脚在大多数情况下会占用太多空间,或者如果您将窗口设置得非常短,它会超出窗口底部。

【问题讨论】:

    标签: wpf layout


    【解决方案1】:

    可能有一种更优雅的方式,但至少在类似的情况下,下面的方法对我有用。它是关于计算 ItemsControl 中所有项目的实际高度,并在需要时调整 Grid 的行高。

    <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;
            }
        }
    

    【讨论】:

    • 发布我的问题后,我环顾四周,发现stackoverflow.com/a/53951023/373915。这个答案更复杂,并且似乎对受到可能在窗口上的其他控件的影响非常敏感。例如,它有一个神奇的数字“50 或围绕窗口标题栏的大小”。我怀疑如果要在 Grid 上方添加另一个控件,则需要更改 50 个控件。但是,我确实喜欢使用 Binding,因此它会在需要时进行更新。您的解决方案要简单得多,但它假设ItemsControl 中的项目都是TextBlocks。
    • 在我的示例中,我只是用TextBlocks 填充了ItemsControl,但在我的实际应用程序中,它是数据绑定的,并使用DataTemplate 来生成实际的视觉控件。 Items 集合有我的数据对象,并且没有 ActualHeight 属性。但是,我找到了ItemContainerGenerator,这似乎解决了这个问题。我发布的答案有点结合了您的解决方案和 Markus 的另一个问题。
    【解决方案2】:

    感谢mami's answer 提供了如何执行此操作的概念,感谢Markus's answer 提供了绑定Row 的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 中,我不需要将 MaxHeight 增加 6,但在我的实际应用程序中,如果我不添加一点额外内容,ScrollViewer 总是显示一个可以滚动的滚动条一点点。不知道是什么导致了这种差异。

    【讨论】:

      猜你喜欢
      • 2014-08-26
      • 2020-04-12
      • 1970-01-01
      • 2017-03-01
      • 1970-01-01
      • 2017-11-25
      • 1970-01-01
      • 2020-08-08
      • 1970-01-01
      相关资源
      最近更新 更多