【问题标题】:How to make scrollviewer work with Height set to Auto in WPF?如何使滚动查看器在 WPF 中将高度设置为自动的情况下工作?
【发布时间】:2020-06-15 21:15:56
【问题描述】:

我了解到,如果ScrollViewer所在的网格行的高度设置为Auto,则垂直滚动条不会生效,因为ScrollViewer的实际大小可以大于视线的高度。因此,为了使滚动条正常工作,我应该将高度设置为固定数字或星形高度

但是,我现在有这个要求,我有两个不同的视图驻留在两个网格行中,并且我有一个切换按钮来在这两个视图之间切换:当一个视图显示时,另一个视图被隐藏/消失。所以我定义了两行,两个高度都设置为Auto。我将每一行中视图的可见性绑定到我的 ViewModel 的布尔属性(一个从 True 转换为 Visible,另一个从 True 转换为 Collapsed。这个想法是当一个视图的可见性是Collapsed,网格行/视图的高度会自动变为0。

视图显示/隐藏工作正常。但是,在一个视图中,我有一个ScrollViewer,正如我提到的,当行高设置为Auto 时它不起作用。谁能告诉我如何在 ScrollViewer 自动工作的同时满足这样的要求?我想我可以在代码隐藏中设置高度。但由于我使用的是 MVVM,因此需要额外的通信/通知。有没有更直接的方法来做到这一点?

【问题讨论】:

    标签: wpf xaml uwp-xaml scrollviewer autosize


    【解决方案1】:

    在 MVVM 中,对我有用的方法是将 ScrollViewer 的高度绑定到父控件的 ActualHeight(始终为 UIElement 类型)。

    ActualHeight 是一个只读属性,仅在控件被绘制到屏幕上后才设置。如果调整窗口大小,它可能会改变。

    <StackPanel>
        <ScrollViewer Height="{Binding Path=ActualHeight, 
               RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}">
            <TextBlock Text=Hello"/>
        </ScrollViewer>
    </StackPanel>
    

    但是如果父控件的高度是无限的呢?

    如果父控件的高度是无限的,那么我们就有更大的问题了。我们必须不断设置所有父母的高度,直到我们碰到一个非无限高度的控件。

    Snoop 在这方面绝对是无价的:

    如果任何 XAML 元素的“高度”是 0NaN,您可以使用以下之一将其设置为:

    • Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
    • VerticalAlignment="Stretch"
    • Height="Auto"

    提示:如果您是带有&lt;RowDefinition Height="*"&gt;Grid 的孩子,请使用VerticalAlignment="Stretch",如果这不起作用,请在其他地方使用Binding RelativeSource...


    如果您有兴趣,以下是我之前解决此问题的所有尝试:

    附录 A:以前的尝试 1

    也可以这样用:

    Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
    

    附录 B:以前的尝试 2

    有用信息:见Auto Height in combination with MaxHeight

    如果似乎没有任何效果,可能是因为父级的 ActualHeight 要么是 0(所以什么都不可见)要么是巨大的(所以滚动查看器永远不需要出现)。如果有深度嵌套的网格,并且底部有一个滚动查看器,这将是一个更大的问题。

    • 使用 Snoop 查找父 StackPanelActualHeight。在属性中,按单词"Actual" 进行过滤,这会返回ActualHeightActualWidth
    • 如果ActualHeight 为零,则使用MinHeight 为其指定一个最小高度,这样我们至少可以看到一些东西。
    • 如果ActualHeight 太大以至于超出屏幕边缘(即16,000),请使用MaxHeight 为其设置一个合理的最大高度,这样滚动条就会出现。

    滚动条出现后,我们可以进一步清理它:

    • StackPanelGridHeight 绑定到父级的ActualHeight

    最后,在这个StackPanel 里面放一个ScrollViewer

    附录 C:以前的尝试 3

    事实证明,这有时会失败:

    Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
    

    原因?如果绑定失败,高度将为零,并且什么也看不到。如果我们绑定到不可访问的元素,绑定可能会失败。如果我们将up 视觉树,然后down 到叶节点(例如,向上到父网格,然后向下到附加到该网格的行的ActualHeight),绑定将失败。这就是为什么绑定到RowDefinitionActualWidth 根本不起作用的原因。

    附录 D:以前的尝试 4

    我最终通过确保从我们到 UserControl 中的第一个 &lt;Grid&gt; 元素的所有父元素的 Height=Auto 来完成这项工作。

    【讨论】:

    • 是的!谢谢!这正是我想到的,但不知道该怎么做。 +1 还用于显示如何绑定到父控件的示例(不使用 ElementName)。我对 WPF 了解得越多,它就越棒。只是因为这个答案才喜欢这个问题
    • 但希望这在尝试绑定到他的“伟大的伟大......爷爷元素”的高度时有效......猜测这就是“AncestorLevel”的用途......是时候谷歌了。
    • 在我的例子中,我有一个由 ScrollViewer 包裹的 StackPanel。 ScrollViewer 被放置在一个网格中,对我来说,它通过绑定 RowDefinition 的 ActualHeight 而不是 Grid 的高度或将 UIElement 定义为 AncestorType 来工作:Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RowDefinition}}"
    【解决方案2】:

    如果可以的话,将高度从 Auto 更改为 *

    例子:

        <Window x:Class="WpfApplication3.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="200" Width="525">
        <StackPanel Orientation="Horizontal"  Background="LightGray">
    
            <Grid Width="100">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll1">
                    <Border Height="300" Background="Red" />
                </ScrollViewer>
                <TextBlock Text="{Binding ElementName=_scroll1, Path=ActualHeight}" Grid.Row="1"/>
            </Grid>
    
            <Grid Width="100">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                    <ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll2">
                        <Border Height="300" Background="Green" />
                    </ScrollViewer>
                <TextBlock Text="{Binding ElementName=_scroll2, Path=ActualHeight}" Grid.Row="1"/>
            </Grid>
        </StackPanel>
    </Window>
    

    【讨论】:

    • 感谢您的回复。但我不太明白你的回答。首先,您试图通过制作两个 Grid 来进行比较,每个 Grid 有两行,一个 Grid 的行高设置为 Auto,另一个 Grid 的行高设置为 *。这是对的吗?但是如果我将 ScrollViewer 的行高设置为 *,它是否总是会占用所有可用空间,即使我想完全隐藏它?我还在更新中发布了我的 XAML 代码。
    • 对不起,我的第一个 cmets 错了。当我将一行的可见性设置为可见而另一行设置为折叠时,将两个行高都设置为 * 确实会显示/隐藏
    【解决方案3】:

    我也遇到过类似的问题,花了几个小时才找到解决方案。解决它的方法是使用 Dockpanel 作为父容器而不是 StackPanel。如果功能应该类似于垂直堆栈面板,只需指定所有子级停靠到顶部。考虑在默认的 Dock XAML 中使用 LastChildFill="False"。

    所以,而不是:

    <StackPanel Orientation="Horizontal">
      <Textbox>SomeTextBox</Textbox>
      <Scrollviewer/>
    </StackPanel>
    尝试:
    <DockPanel LastChildFill="False">
      <Textbox DockPanel.Dock="Top">SomeTextBox</Textbox>
      <Scrollviewer DockPanel.Dock="Top"/>
    </DockPanel>

    【讨论】:

      【解决方案4】:

      您可以在 ScrollViewer 上设置固定高度,但是您必须考虑网格的第二行也将具有该高度,因为行的第一个子项将是 ScrollViewer 并且行的高度是自动的,或者您绑定高度ScrollViewer 到布局中的另一个控件。我们不知道您的布局看起来如何。

      最后,如果您都不喜欢两者,只需按照 swiszcz 的建议将行高设置为 *,或者编写您自己的自定义面板,该面板将能够在每个平行宇宙或类似的东西中布局所有可能的内容。 :)

      【讨论】:

        【解决方案5】:

        我发现,您必须将 ScrollViewer 放在具有 Height=Auto 的容器中,或者您将其父 Heigh Actual Size 应用到该容器中。

        在我的情况下,我有 UserControl 喜欢

          <Grid Margin="0,0,0,0" Padding="0,2,0,0">
                            <ScrollViewer Height="Auto" ZoomMode="Disabled" IsVerticalScrollChainingEnabled="True"  VerticalAlignment="Top"
                             HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Disabled"
                             VerticalScrollMode="Enabled" VerticalScrollBarVisibility="Visible"> 
                                <ListView  ItemsSource="{x:Bind PersonalDB.View, Mode=OneWay}" x:Name="DeviceList"
                                   ScrollViewer.VerticalScrollBarVisibility="Hidden" 
                            ItemTemplate="{StaticResource ContactListViewTemplate}"
                            SelectionMode="Single"
                            ShowsScrollingPlaceholders="False"
                            Grid.Row="1" 
                            Grid.ColumnSpan="2"
                            VerticalAlignment="Stretch"
                            BorderThickness="0,0,0,0"
                            BorderBrush="DimGray">
                                    <ListView.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <ItemsStackPanel AreStickyGroupHeadersEnabled="False" />
                                        </ItemsPanelTemplate>
                                    </ListView.ItemsPanel>
                                    <ListView.GroupStyle>
                                        <GroupStyle>
                                            <GroupStyle.HeaderTemplate>
                                                <DataTemplate x:DataType="local1:GroupInfoList">
                                                    <TextBlock Text="{x:Bind Key}" 
                                               Style="{ThemeResource TitleTextBlockStyle}"/>
                                                </DataTemplate>
                                            </GroupStyle.HeaderTemplate>
                                        </GroupStyle>
                                    </ListView.GroupStyle>
                                </ListView>
                            </ScrollViewer>
                        </Grid> 
        

        然后我将它动态添加到ContentControl,它位于Page 中。

         <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,12,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="70"  /> 
                    <RowDefinition Height="*" MinHeight="200"  />
                </Grid.RowDefinitions> 
         <Grid Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"     >
                    <ContentControl x:Name="UIControlContainer"  />
                </Grid>
            </Grid>
        

        注意Row 中的Heigh*

        当我填充 ContentControl 时,我在 Loaded 事件中使用此代码

          UIControlContainer.Content = new UIDeviceSelection() { 
        VerticalAlignment = VerticalAlignment.Stretch,
        HorizontalAlignment = HorizontalAlignment.Stretch,
        Height = UIControlContainer.ActualHeight,
        Width = UIControlContainer.ActualWidth
        };
        

        ContentControl 改变它的大小时,你必须更新UserControl 的大小。

         UIControlContainer.SizeChanged += UIControlContainer_SizeChanged;
        
         private void UIControlContainer_SizeChanged(object sender, SizeChangedEventArgs e)
                {
                    if (UIControlContainer.Content != null)
                    {
                        if (UIControlContainer.Content is UserControl)
                        {
                            (UIControlContainer.Content as UserControl).Height = UIControlContainer.ActualHeight;
                            (UIControlContainer.Content as UserControl).Width = UIControlContainer.ActualWidth;
                        }
                    }
                }
        

        享受吧!

        附:其实我是为 UWP 做的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-12-10
          • 2011-09-07
          • 1970-01-01
          • 1970-01-01
          • 2018-08-06
          • 2019-09-22
          相关资源
          最近更新 更多