WPF Scrollviewer DesiredSize не увеличивается, когда ScrollBar становится видимым

У меня есть WPF UserControl (внутри ElementHost) с ScrollViewer который содержит ItemsControl, HorizontalScrollbarVisibility установлен в Auto, поэтому, если прокрутка не требуется, ScrollBarстановится скрытым

Мое требование заключается в том, что если ScrollBar будет показан / скрыт, ElementHost регулирует его высоту соответственно. Чтобы достичь этого, я слушаю SizeChanged событие, я получаю DesiredSize из ScrollViewer в EventHandlerпотом прохожу DesiredSize.Height к ElementHost,

  1. видимый 2. скрытый 3. неверно, наложено

Один способ, это работает: с ScrollBar видим (ситуация 1), я увеличиваю свое окно, пока все элементы ItemsControl видны ScrollBar исчезает, ElementHost приспосабливается к уменьшенной высоте (ситуация 2). DesiredSize на самом деле стало меньше, как только ScrollBar скрыт.

Другой способ, однако, не работает: с ScrollBar не виден (ситуация 2), я уменьшаю размер окна до ScrollBar необходимо и появляется. DesiredSize остается прежним, а ElementHost не регулируется (ситуация 3).

Есть идеи?

Это XAML из Scrollviewerс некоторыми вещами MVVM, но не зацикливайтесь на этом, дело в том, почему DesiredSize не увеличивается, когда ScrollBar появляется? Почему это только сжатие?

<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
    <i:Interaction.Behaviors>
        <beh:HeightChangedBehavior HeightChangedCommand="{Binding HeightChangedCommand}" />
    </i:Interaction.Behaviors>
    <ItemsControl ItemsSource="{Binding TabHeaders}" >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="models:TabHeaderButtonModel">
                <RadioButton Content="{Binding Caption}" IsChecked="{Binding IsChecked, Mode=TwoWay}" GroupName="Tabs" 
                            Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding}"
                            Style="{StaticResource TabHeaderToggleButtonStyle}">
                </RadioButton>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

Стиль ScrollViewer (в основном WPF по умолчанию):

<Style x:Key="ScrollViewerStyle1" TargetType="{x:Type ScrollViewer}">
    <Setter Property="Template" >
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ScrollViewer}">
                <Grid x:Name="Grid" Background="{TemplateBinding Background}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
                    <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
                    <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
                    <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}" Style="{DynamicResource ScrollBarStyle1}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="White"/>
        </Trigger>
    </Style.Triggers>
</Style>

1 ответ

Решение

Мне нужно было рассчитать желаемую высоту, получив желаемую высоту содержимого ScrollViewer и добавив высоту ScrollBar, если она видна.

Мне все еще немного неловко, поэтому, если у вас есть лучшее решение, я с удовольствием изменю принятый ответ.

public class HeightChangedBehavior : Behavior<ScrollViewer>
{
    public ICommand HeightChangedCommand { get { return (ICommand)GetValue(HeightChangedCommandProperty); } set { SetValue(HeightChangedCommandProperty, value); } }
    public static readonly DependencyProperty HeightChangedCommandProperty = DependencyProperty.Register("HeightChangedCommand", typeof(ICommand), typeof(HeightChangedBehavior), new PropertyMetadata(null));


    protected override void OnAttached()
    {
        this.AssociatedObject.ScrollChanged += AssociatedObject_ScrollChanged;
        base.OnAttached();
    }

    /// <summary>
    /// Calculates the desired height for the scrollviewer, as the sum of its content
    /// desired height and, if visible, the horizontal scrollbar height.
    /// </summary>
    void AssociatedObject_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        ScrollViewer sv = (ScrollViewer)sender;

        // get content height
        double height = ((FrameworkElement)sv.Content).DesiredSize.Height;

        if (sv.ComputedHorizontalScrollBarVisibility == Visibility.Visible)
        {
            // add scrollbar height
            height += (double)sv.FindResource(SystemParameters.HorizontalScrollBarHeightKey); // template of scrollbar should use this key
        }

        int intHeight = (int)Math.Ceiling(height); // whole pixels

        // execute the command
        ICommand cmd = this.HeightChangedCommand;
        if (cmd != null && intHeight != sv.ActualHeight)
            cmd.Execute(intHeight);
    }
}
Другие вопросы по тегам