如何使WPF网格行高自适应而不超出窗口高度?

5
我有一个窗口,其中包含一个ItemsControl,里面可以有不定数量的控件。为了考虑到当控件数超过窗口高度时的情况,我将其包装在ScrollViewer中,这样当项目数超过可用高度时,会显示滚动条。
现在,问题在于有时ItemsControl中没有任何内容,有时则有。因此,我将网格行的高度设置为Auto,以允许ItemsControl在为空时消失或在需要时增长。然而,这意味着该行需要多少高度就占用多少高度,即使这超过了窗口高度,垂直滚动条也永远不会显示。
下面是一些XAML示例窗口的代码,演示了这个问题...
<Window x:Class="DuplicateCustomerCheck.TestScrollViewerWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test Scroll Viewer Window"
        Height="450"
        Width="200">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBox Name="N"
             TextChanged="TextBoxBase_OnTextChanged"
             Grid.Row="0"
             Margin="3" />

    <Grid Margin="3"
          Grid.Row="1">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <TextBlock Text="Possible duplicate of..."
                 Margin="3" />
      <ScrollViewer VerticalScrollBarVisibility="Visible"
                    Grid.Row="1">

        <ItemsControl Name="MatchingNames"
                      ItemsSource="{Binding MatchingNames, Mode=TwoWay}">
          <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
              <StackPanel Orientation="Vertical" />
            </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>

          <ItemsControl.ItemTemplate>
            <DataTemplate>
              <Button Content="{Binding Item}" />
            </DataTemplate>
          </ItemsControl.ItemTemplate>
        </ItemsControl>
      </ScrollViewer>
    </Grid>

    <TextBlock Grid.Row="2"
               Margin="3"
               Text="Stuff at the bottom" />
  </Grid>
</Window>

为了演示目的,这里是按钮的事件处理程序,允许我测试不同数目的项目(请注意,这是简单粗暴的代码,因此没有错误检查等)...

private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e) {
  MatchingNames.ItemsSource = Enumerable
    .Range(0, int.Parse(N.Text))
    .Select(n1 => new {
      Item = "Button " + n1
    });
}

如果我将第二行网格的高度更改为*,那么它就能正常工作,但这意味着ItemsControl将永久可见,而我不想要这样。它应该仅在其中有一些项时才显示。
我尝试了此博客文章中的ScrollViewerMaxSizeBehavior行为(代码在这里),但没有任何效果。
有人知道如何允许ItemsControl使用它所需的所有垂直空间(包括零),但不要增长到超出窗口大小吗?

是由于ScrollViewer的VerticalScrollbarVisibility属性被设置为“Visible”所引起的吗? - kennyzx
@Andy 我尝试设置了MaxHeight,但是它仍然超出了窗口的高度。我不确定如何使用您建议的多绑定和多转换器,但这并不像简单地减去文本块的高度那么简单。正如我所提到的,这只是一个示例,在实际窗口中,这部分XAML被深深地嵌套在网格等层次结构中。如果可以的话,我还能使用您的建议吗?如果可以,请给我一些想法,因为我认为这对我来说有点超纲了。谢谢 - DreamingOfSleep
当ItemsControl为空时,将其可见性设置为Collapsed如何? - mami
@mami 不确定那会如何有帮助。问题是当它可见时,而不是隐藏时。我想限制高度,以便它不会超出窗口高度。 - DreamingOfSleep
我的意思是使用您所说的*高度,并在没有项目的情况下隐藏控件。 - mami
显示剩余2条评论
2个回答

1

这种情况很难只用XAML解决。我会通过一些计算来解决它...

<Grid x:Name="MyGrid">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" MaxHeight="{Binding Row2MaxHeight}"/>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBox Name="N" TextChanged="TextBoxBase_OnTextChanged" Grid.Row="0" Margin="3" />

    <Grid Margin="3" Grid.Row="1" x:Name="MyInnerGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBlock Text="Possible duplicate of..." Margin="3" />
        <ScrollViewer Grid.Row="1" MaxHeight="{Binding Row2MaxHeightInner}">
            <ItemsControl Name="MatchingNames" ItemsSource="{Binding MatchingNames, Mode=TwoWay}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Vertical" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>

                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button Content="{Binding Item}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

    <TextBlock Grid.Row="2"
               Margin="3"
               Text="Stuff at the bottom" />
</Grid>

代码如下:

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

        DataContext = this;
        SizeChanged += SizeWasChanged;
    }

    private void SizeWasChanged(object sender, SizeChangedEventArgs e) {
        OnPropertyChanged(nameof(Row2MaxHeight));
        OnPropertyChanged(nameof(Row2MaxHeightInner));
    }

    public double Row2MaxHeight => ActualHeight - MyGrid.RowDefinitions[0].ActualHeight - MyGrid.RowDefinitions[2].ActualHeight - 50; //50 or something is around the Size of the title bar of the window
    public double Row2MaxHeightInner => Row2MaxHeight - MyInnerGrid.RowDefinitions[0].ActualHeight - 6; //6 should match the margin of the scrollviewer

    private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e) {
        MatchingNames.ItemsSource = Enumerable
            .Range(0, int.Parse(N.Text))
            .Select(n1 => new {
                Item = "Button " + n1
            });
        OnPropertyChanged(nameof(Row2MaxHeight));
        OnPropertyChanged(nameof(Row2MaxHeightInner));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

0

我在发布了类似的问题之后发现了这个问题。至少我认为我们在问同样的事情,尽管你提到“因此,我将网格行的高度设置为自动,以允许ItemsControl在为空时消失或在需要时增长。” 然而,当我尝试你的示例时,即使是空的,ScrollViewer仍然显示它的滚动条并占用空间。

无论如何,mami回答了我的问题,虽然他们的答案对我来说并不完全有效,但连同Markus的答案一起,它引导我想出了我的答案

我的答案可能与你的情况不太适用,因为我的答案假设ItemsControlGrid行中唯一的东西,而你在该行中还有"Possible duplicate of..." TextBlock。我修改了我的答案以考虑TextBlock的大小,但它并不像我想要的那么干净。作为一个可能的优化,当计算ItemsControlItem的总高度时,你可能可以在高度达到“足够大”(例如大于Window的高度)时提前退出。这样,如果你有数千个项目,只有几十个能真正适合屏幕,你就可以得到几十个项目的高度而不是数千个。

无论如何,也许它会给你一些灵感 :)

XAML:

<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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" MaxHeight="{Binding ItemMaxHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBox Name="N"
             TextChanged="TextBoxBase_OnTextChanged"
             Grid.Row="0"
             Margin="3" />

        <Grid Margin="3"
          Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <TextBlock Name="tb" Text="Possible duplicate of..."
                 Margin="3" />
            <ScrollViewer VerticalScrollBarVisibility="Visible"
                    Grid.Row="1">

                <ItemsControl Name="MatchingNames"
                      ItemsSource="{Binding MatchingNames, Mode=TwoWay}"
                      SizeChanged="MatchingNames_SizeChanged">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Vertical" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>

                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Button Content="{Binding Item}" />
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </ScrollViewer>
        </Grid>

        <TextBlock Grid.Row="2"
               Margin="3"
               Text="Stuff at the bottom" />
    </Grid>
</Window>

代码后台:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
    {
        MatchingNames.ItemsSource = Enumerable
          .Range(0, int.Parse(N.Text))
          .Select(n1 => new
          {
              Item = "Button " + n1
          });
    }

    public double ItemMaxHeight
    {
        get
        {
            if (MatchingNames == null)
                return double.PositiveInfinity;

            double height = 0.0;
            var icg = MatchingNames.ItemContainerGenerator;
            for (int i = 0; i < MatchingNames.Items.Count; i++)
                height += (icg.ContainerFromIndex(i) as FrameworkElement).ActualHeight;

            return height 
                + tb.Margin.Top + tb.ActualHeight + tb.Margin.Bottom
                + 6.0; // 6 should match the margin of the scrollviewer
        }
    }

    private void MatchingNames_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (e.HeightChanged)
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ItemMaxHeight"));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接