Как сделать так, чтобы строка сетки WPF имела высоту Авто, не превышая высоту окна? - PullRequest
0 голосов
/ 27 декабря 2018

У меня есть окно, содержащее 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 занимать столько вертикального пространства, сколько ему нужно, включая ноль, но не расти выше, чем может поместиться в окне?

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Я нашел этот вопрос после того, как опубликовал аналогичный вопрос .По крайней мере, я думаю , что мы просим то же самое, хотя вы упоминаете: «Поэтому я установил высоту строки сетки на Авто, чтобы позволить ItemsControl исчезать, когда он пуст, или увеличиваться, когда это необходимо».Однако, когда я попробовал ваш образец, даже когда он пуст, ScrollViewer по-прежнему показывает полосы прокрутки и занимает место.

В любом случае mami ответили на мой вопрос , и хотяу меня их ответ не совсем сработал, он вместе с ответом Маркуса заставил меня придумать мой собственный ответ .

мой ответ не совсем работаетс вашей ситуацией, так как моя предполагает, что ItemsControl является единственной вещью в строке Grid, в то время как у вас также есть «Возможная копия ...» TextBlock в строке.Я подправил свой ответ, чтобы учесть размер TextBlock, но он не такой чистый, как хотелось бы.В качестве возможной оптимизации, при вычислении общей высоты ItemsControl Item s, вы, вероятно, могли бы выйти рано, как только высота станет «достаточно большой» (например, больше, чем высота 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"));
    }
}
0 голосов
/ 28 декабря 2018

Эту ситуацию сложно решить только с 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));
    }
}
...