Monday, May 05, 2008

Creating 'Expression Blend' ScrollBars in WPF

The scroll bars in Expression Blend are very nice. Nice enough that I thought I’d spend some time this afternoon figuring out how I could modify the WPF scroll bar control template. Fortunately, it turned out that there really wasn’t that much work involved. Here’s the result:



I started off by grabbing the template from
MSDN. The scroll bar template consists of a style that brings into play either the VerticalScrollBar template or the HorizontalScrollBar template via the Orientation dependency property as seen:



The remaining discussion will only focus on the VerticalScrollBar, since the solution is almost exactly the same for HorizontalScrollBar.

The RepeatButton defined in the VerticalScrollBar template seen at Grid="0" and again at Grid="2" defines the up arrow and down arrow respectively. I made these a little more elongated to mirror the Blend arrows by changing the content properties as follows:

Top arrow: Content="M 0 8 L 8 8 L 4 0 Z"
Bottom arrow: Content="M 0 0 L 4 8 L 8 0 Z"



The scroll thumb also needed some attention. The shape of the thumb at both ends is more rounded than the default, so I increased the CornerRadius:
CornerRadius="4"

Also, the thumb in blend has no border, so I removed the BorderBrush and set the:

BorderThickness="0"

Another difference is that the thumb is highlighted when the mouse is over it and changes colour to black while it is being dragged. Both of these are dependency properties that can be modified to the appropriate values with triggers:




Notice that I had to give the Border a name ‘roundedBorder’ so that the triggers could reference it.

ScrollBarLineButton defines the style and template for the repeater button that houses the arrows (which will end up being up or down for vertical scroll bar. And left or right for a horizontal scroll bar). The template for ScrollBarLineButton acquires the visual for the arrow by databinding to the Content property of the template parent – a neat trick that means only one ScrollBarLineButton needs to be defined for the whole template to be able to deal with all four arrows.


To get the ScrollBarLineButton to look like the blend version, I had to make a few more changes. The arrow is highlighted when the mouse is over it – in much the same way as the thumb. To achieve this, I gave the Path a name ‘glyph’ and then changed the trigger ‘IsMouseOver’ to retarget to ‘glyph’ Fill to the colour: GlyphMouseOver. The IsPressed trigger also needs to mirror the thumb and sets the ‘glyph’ Fill to black.



Finally, I also had to change some of the brushes defined for the scrollbar. Here are the changed versions:
<SolidColorBrush x:Key="GlyphBrush"Color="#FF7E7C7C" />
<SolidColorBrush x:Key="GlyphMouseOver"Color="#FFE8E8E8"/>
<SolidColorBrush x:Key="PressedBrush" Color="Black"/>
<SolidColorBrush x:Key="NormalBrush"Color="#585858"/>

7 comments:

Anonymous said...

Can you post a link to the xaml file?

Anonymous said...

There's a reference to a "{StaticResource ScrollBarPageButton}" in the ControlTemplate for the VerticalScrollBar but there's no xaml that defines it...can you please supply it???

Also, it would have been good to post the xaml as text, not .jpg which can't be copied.

Thanks!

Steve J Rodgers said...

Here's the XAML across 2 files (ScrollBar.xaml & ScrollBarColors.xaml):

-----------------
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ScrollBarLineButton" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border
Name="Border"
Margin="1"
CornerRadius="0"
Background="{StaticResource NormalBrush}"
BorderBrush="{StaticResource NormalBrush}"
BorderThickness="1">
<Path
Name="glyph"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{StaticResource GlyphBrush}"
Data="{Binding Path=Content,RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="glyph" Property="Fill" Value="{StaticResource GlyphMouseOver}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="glyph" Property="Fill" Value="{StaticResource PressedBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<Style x:Key="ScrollBarPageButton" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<Style x:Key="ScrollBarThumb" TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle
Name="rectThumb"
RadiusX="3"
RadiusY="3"

Fill="{TemplateBinding Background}"
/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="rectThumb" Property="Fill" Value="{StaticResource GlyphMouseOver}"/>
</Trigger>
<Trigger Property="IsDragging" Value="true">
<Setter TargetName="rectThumb" Property="Fill" Value="Black"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid >
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18"/>
<RowDefinition Height="0.0001*"/>
<RowDefinition MaxHeight="18"/>
</Grid.RowDefinitions>
<Border
Grid.RowSpan="3"
CornerRadius="0"
Background="{StaticResource NormalBrush}" />
<RepeatButton
Grid.Row="0"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineUpCommand"
Content="M 0 8 L 8 8 L 4 0 Z" />
<Track
Name="PART_Track"
Grid.Row="1"
IsDirectionReversed="true"

>
<Track.DecreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>

<Track.Thumb>
<Thumb
Style="{StaticResource ScrollBarThumb}"
Margin="5,0,5,0"
Background="{StaticResource GlyphBrush}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton
Grid.Row="3"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineDownCommand"
Content="M 0 0 L 4 8 L 8 0 Z"/>
</Grid>
</ControlTemplate>

<ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="18"/>
<ColumnDefinition Width="0.00001*"/>
<ColumnDefinition MaxWidth="18"/>
</Grid.ColumnDefinitions>
<Border
Grid.ColumnSpan="3"
CornerRadius="0"
Background="{StaticResource NormalBrush}" />
<RepeatButton
Grid.Column="0"
Style="{StaticResource ScrollBarLineButton}"
Width="18"
Command="ScrollBar.LineLeftCommand"
Content="M 8 0 L 8 8 L 0 4 Z" />
<Track
Name="PART_Track"
Grid.Column="1"
IsDirectionReversed="False">
<Track.DecreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageLeftCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb
Style="{StaticResource ScrollBarThumb}"
Margin="0,5,0,5"
Background="{StaticResource GlyphBrush}"
BorderBrush="{StaticResource NormalBorderBrush}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton
Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageRightCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton
Grid.Column="3"
Style="{StaticResource ScrollBarLineButton}"
Width="18"
Command="ScrollBar.LineRightCommand"
Content="M 0 0 L 8 4 L 0 8 Z"/>
</Grid>
</ControlTemplate>

<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="18" />
<Setter Property="Template" Value="{StaticResource HorizontalScrollBar}" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Width" Value="18"/>
<Setter Property="Height" Value="Auto" />
<Setter Property="Template" Value="{StaticResource VerticalScrollBar}" />
</Trigger>
</Style.Triggers>
</Style>

</ResourceDictionary>
-----------------

+++++++++++++++++
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="NormalBrush" Color="#585858"/>

<LinearGradientBrush x:Key="HorizontalNormalBrush" StartPoint="0,0" EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#CCC" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#EEE" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="HorizontalLightBrush" StartPoint="0,0" EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#EEE" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DarkBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#AAA" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<SolidColorBrush x:Key="PressedBrush" Color="Black"/>

<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />

<SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />

<SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />

<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />

<!-- Border Brushes -->

<LinearGradientBrush x:Key="NormalBorderBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#CCC" Offset="0.0"/>
<GradientStop Color="#444" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="HorizontalNormalBorderBrush" StartPoint="0,0" EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#CCC" Offset="0.0"/>
<GradientStop Color="#444" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultedBorderBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#777" Offset="0.0"/>
<GradientStop Color="#000" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="PressedBorderBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#444" Offset="0.0"/>
<GradientStop Color="#888" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>

<SolidColorBrush x:Key="DisabledBorderBrush" Color="#AAA" />

<SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />

<SolidColorBrush x:Key="LightBorderBrush" Color="#AAA" />

<!-- Miscellaneous Brushes -->
<SolidColorBrush x:Key="GlyphBrush" Color="#FF7E7C7C" />

<SolidColorBrush x:Key="GlyphMouseOver" Color="#FFE8E8E8"/>

<SolidColorBrush x:Key="LightColorBrush" Color="#DDD" />

</ResourceDictionary>
+++++++++++++++++

Anonymous said...

Great article. Thanks.

Rose Vincent said...

Superb article.. thank you Steve..
:-). helped me a lott..

KoG said...

great choice for example box content ^_^ That's an awesome song...

Anonymous said...

Thank you so much.
It helped me a lot.