如何在不超过窗口高度的情况下使WPF网格行具有自动高度?

时间:2018-12-27 18:54:02

标签: c# wpf xaml itemscontrol

我有一个窗口,其中包含一个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是永久可见的,这是我不希望的。仅当其中有某些项目时才显示。

我尝试了this blog postcode here)的ScrollViewerMaxSizeBehavior行为,但是没有任何区别。

任何人都知道我如何允许ItemsControl占用所需的垂直空间,包括零,但又不会变得超出窗口的高度吗?

2 个答案:

答案 0 :(得分:0)

仅使用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));
    }
}

答案 1 :(得分:0)

发布a similar question后,我发现了这个问题。至少我想,我们在问同样的事情,尽管您提到“因此,我将网格行的高度设置为“自动”,以使ItemsControl在为空时消失,或在需要时增长。”但是,当我尝试采样时,即使是空的,ScrollViewer仍在显示其滚动条,并且占用了空间。

无论如何,mami answered my question尽管他们的答案对我来说并不完全有效,但它与Markus's answer一起使我提出了my own answer

但是,我的答案与您的情况不太吻合,因为我假设ItemsControlGrid行中的唯一内容,而您却拥有“ ...的可能重复项”行中的TextBlock。我调整了答案以考虑TextBlock的大小,但是它并不像我希望的那样干净。作为一种可能的优化,在计算ItemsControl的{​​{1}}的总高度时,一旦高度达到“足够大”(例如,比Item大),您可能会提早退出。的高度)。这样,如果您有成千上万个项目,而实际上只有几十个可以放到屏幕上,那么您只需获得几十个而不是数千个的高度即可。

无论如何,也许它会给您一些想法:)

XAML:

Window

后面的代码:

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