Le cadre fantômes du ScrollViewer

Si vous avez déjà utilisé un ScrollViewer vous êtes forcément déjà tomber sur ce cas un peu étrange. Un cadre fantôme apparait entourant l’intérieur du Scrollviewer lorsqu’on le scrolle.
Rien de bien méchant, mais pas très esthétique, comme en témoigne le screenshot suivant (le fond est en bleu pour faire ressortir le fameux cadre) :

Cet exemple a été généré à partir du code suivant :

        <ScrollViewer Background="Blue" Width="200" Height="100">
            <StackPanel>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
            </StackPanel>                                
        </ScrollViewer>      

Pourtant pas besoin des GhostBusters pour comprendre ce qui se passe.
En regardant dans le template du ScrollViewer (via Blend par exemple) on obtient ceci :

		<Style x:Key="ScrollViewerStyle1" TargetType="ScrollViewer">
			<Setter Property="HorizontalContentAlignment" Value="Left"/>
			<Setter Property="VerticalContentAlignment" Value="Top"/>
			<Setter Property="VerticalScrollBarVisibility" Value="Visible"/>
			<Setter Property="Padding" Value="4"/>
			<Setter Property="BorderThickness" Value="1"/>
			<Setter Property="BorderBrush">
				<Setter.Value>
					<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
						<GradientStop Color="#FFA3AEB9" Offset="0"/>
						<GradientStop Color="#FF8399A9" Offset="0.375"/>
						<GradientStop Color="#FF718597" Offset="0.375"/>
						<GradientStop Color="#FF617584" Offset="1"/>
					</LinearGradientBrush>
				</Setter.Value>
			</Setter>
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="ScrollViewer">
						<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2">
							<Grid Background="{TemplateBinding Background}">
								<Grid.ColumnDefinitions>
									<ColumnDefinition Width="*"/>
									<ColumnDefinition Width="Auto"/>
								</Grid.ColumnDefinitions>
								<Grid.RowDefinitions>
									<RowDefinition Height="*"/>
									<RowDefinition Height="Auto"/>
								</Grid.RowDefinitions>
								<ScrollContentPresenter x:Name="ScrollContentPresenter" Cursor="{TemplateBinding Cursor}" Margin="{TemplateBinding Padding}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
								<Rectangle Fill="#FFE9EEF4" Grid.Column="1" Grid.Row="1"/>
								<ScrollBar x:Name="VerticalScrollBar" IsTabStop="False" Margin="0,-1,-1,-1" Width="18" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Grid.Column="1" Grid.Row="0" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Value="{TemplateBinding VerticalOffset}" Orientation="Vertical" ViewportSize="{TemplateBinding ViewportHeight}"/>
								<ScrollBar x:Name="HorizontalScrollBar" IsTabStop="False" Height="18" Margin="-1,0,-1,-1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Grid.Column="0" Grid.Row="1" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Value="{TemplateBinding HorizontalOffset}" Orientation="Horizontal" ViewportSize="{TemplateBinding ViewportWidth}"/>
							</Grid>
						</Border>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>

Le ScrollContentPresenter du Template du ScrollViewer contient cet attribut intéressant dans notre cas :

Margin="{TemplateBinding Padding}"

Le padding est directement greffé sur le contenu du ScrollViewer et celui-ci à une valeur par défaut de 4.
Pourquoi ce padding a été implementé ici, mystère ! Mais c’est bien lui, en revanche, le coupable de notre cadre inesthétique.

Pour contrer l’effet simplement, on peut soit modifier le style et fixer la valeur par défaut du padding à 0 soit modifier notre exemple de tout à l’heure comme suit :

        <ScrollViewer Background="Blue" Width="200" Height="100" Padding ="0">
            <StackPanel Margin="4">
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
                <Button Content="Hello" Height="32"></Button>
            </StackPanel>                                
        </ScrollViewer>      

Notez le padding à 0 du ScrollViewer ainsi que le Margin à 4 du StackPanel qui font toute la différence…

C’est nickel !

leave your comment