WPF Scrollviewer DesiredSize не увеличивается, когда ScrollBar становится видимым
У меня есть WPF UserControl
(внутри ElementHost
) с ScrollViewer
который содержит ItemsControl
, HorizontalScrollbarVisibility
установлен в Auto
, поэтому, если прокрутка не требуется, ScrollBar
становится скрытым
Мое требование заключается в том, что если ScrollBar
будет показан / скрыт, ElementHost
регулирует его высоту соответственно. Чтобы достичь этого, я слушаю SizeChanged
событие, я получаю DesiredSize
из ScrollViewer
в EventHandler
потом прохожу DesiredSize.Height
к ElementHost
,
- 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);
}
}