问题
I have a WPF UserControl
(inside an ElementHost
) with a ScrollViewer
that contains an ItemsControl
. The HorizontalScrollbarVisibility
is set to Auto
, so if no scrolling is necessary, the ScrollBar
gets hidden.
My requirement is, that if the ScrollBar
gets shown/hidden, the ElementHost
does adjust it's height accordingly. To achieve that, I'm listening to the SizeChanged
event, I get the DesiredSize
of the ScrollViewer
in the EventHandler
, then I pass DesiredSize.Height
to the ElementHost
.
- 2. 3.
One way, this works: With ScrollBar
visible (situation 1), I enlarge my window until all items of the ItemsControl
are visible, the ScrollBar
disappears, the ElementHost
adjusts to reduced height (situation 2). DesiredSize
actually got smaller the moment the ScrollBar
is hidden.
The other way, though, it doesn't work: With ScrollBar
not visible (situation 2), I reduce my window size until a ScrollBar
is necessary and appears. DesiredSize
stays the same, and the ElementHost
does not adjust (situation 3).
Any ideas?
This is the xaml of the Scrollviewer
, with some MVVM stuff, but don't get hung up on this, the point really is, why does the DesiredSize
not increase when the ScrollBar
appears? Why is it shrink only?
<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 style (basically default 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:
I had to calculate the desired height by getting the ScrollViewer's content desired height and adding the ScrollBar height if visible.
This still feels slightly awkward to me, so if you have a better solution, I'll gladly change the accepted answer.
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);
}
}
来源:https://stackoverflow.com/questions/14963104/wpf-scrollviewer-desiredsize-does-not-increase-when-scrollbar-gets-visible