WPF控件样式和模板全解析-ScrollViewer控件
ScrollViewer和ScrollBar
ScrollViewer控件也是一个内容控件,但是它和前面介绍的Button、Label等内容控件不太一样。
它更像一个容器,用于创建可滚动区域。一个 ScrollViewer 只能有一个子元素,通常会使用一个面板来做为ScrollViewer的子元素。

ScrollViewer控件包含了水平和垂直两部分的滚动条区域,这两个滚动条区域是由ScrollBar控件来实现。
如下所示:

所以本文会把ScrollViewer和ScrollBar两个控件的样式放在一起来进行讲解。
ScrollViewer默认样式和模板
下面依旧是使用Blend来查看ScrollViewer的默认样式和模板
1 <Style x:Key="ScrollViewerStyle1" TargetType="{x:Type ScrollViewer}"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate TargetType="{x:Type ScrollViewer}"> 5 <Grid x:Name="Grid" Background="{TemplateBinding Background}"> 6 <Grid.ColumnDefinitions> 7 <ColumnDefinition Width="*"/> 8 <ColumnDefinition Width="Auto"/> 9 </Grid.ColumnDefinitions> 10 <Grid.RowDefinitions> 11 <RowDefinition Height="*"/> 12 <RowDefinition Height="Auto"/> 13 </Grid.RowDefinitions> 14 <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/> 15 <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanHorizontallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" CanVerticallyScroll="False" Grid.Column="0" Content="{TemplateBinding Content}" CanContentScroll="{TemplateBinding CanContentScroll}" Margin="{TemplateBinding Padding}" Grid.Row="0"/> 16 <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/> 17 <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/> 18 </Grid> 19 </ControlTemplate> 20 </Setter.Value> 21 </Setter> 22 <Style.Triggers> 23 <Trigger Property="IsEnabled" Value="false"> 24 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> 25 </Trigger> 26 </Style.Triggers> 27 </Style>
ScrollBar的默认样式和模板
ScrollBar的默认样式和模板
1 <Style x:Key="FocusVisual"> 2 <Setter Property="Control.Template"> 3 <Setter.Value> 4 <ControlTemplate> 5 <Rectangle Margin="2" StrokeDashArray="1 2" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" StrokeThickness="1"/> 6 </ControlTemplate> 7 </Setter.Value> 8 </Setter> 9 </Style> 10 <SolidColorBrush x:Key="ScrollBar.Static.Background" Color="#F0F0F0"/> 11 <SolidColorBrush x:Key="ScrollBar.Static.Border" Color="#F0F0F0"/> 12 <SolidColorBrush x:Key="ScrollBar.Static.Glyph" Color="#606060"/> 13 <SolidColorBrush x:Key="ScrollBar.Static.Thumb" Color="#CDCDCD"/> 14 <SolidColorBrush x:Key="ScrollBar.MouseOver.Background" Color="#DADADA"/> 15 <SolidColorBrush x:Key="ScrollBar.MouseOver.Border" Color="#DADADA"/> 16 <SolidColorBrush x:Key="ScrollBar.MouseOver.Glyph" Color="#000000"/> 17 <SolidColorBrush x:Key="ScrollBar.MouseOver.Thumb" Color="#A6A6A6"/> 18 <SolidColorBrush x:Key="ScrollBar.Pressed.Background" Color="#606060"/> 19 <SolidColorBrush x:Key="ScrollBar.Pressed.Border" Color="#606060"/> 20 <SolidColorBrush x:Key="ScrollBar.Pressed.Thumb" Color="#606060"/> 21 <SolidColorBrush x:Key="ScrollBar.Pressed.Glyph" Color="#FFFFFF"/> 22 <SolidColorBrush x:Key="ScrollBar.Disabled.Background" Color="#F0F0F0"/> 23 <SolidColorBrush x:Key="ScrollBar.Disabled.Border" Color="#F0F0F0"/> 24 <SolidColorBrush x:Key="ScrollBar.Disabled.Glyph" Color="#BFBFBF"/> 25 <Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}"> 26 <Setter Property="OverridesDefaultStyle" Value="true"/> 27 <Setter Property="Background" Value="Transparent"/> 28 <Setter Property="Focusable" Value="false"/> 29 <Setter Property="IsTabStop" Value="false"/> 30 <Setter Property="Template"> 31 <Setter.Value> 32 <ControlTemplate TargetType="{x:Type RepeatButton}"> 33 <Rectangle Fill="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"/> 34 </ControlTemplate> 35 </Setter.Value> 36 </Setter> 37 </Style> 38 <Style x:Key="ScrollBarButton" TargetType="{x:Type RepeatButton}"> 39 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> 40 <Setter Property="BorderThickness" Value="1"/> 41 <Setter Property="HorizontalContentAlignment" Value="Center"/> 42 <Setter Property="VerticalContentAlignment" Value="Center"/> 43 <Setter Property="Padding" Value="1"/> 44 <Setter Property="Focusable" Value="false"/> 45 <Setter Property="IsTabStop" Value="false"/> 46 <Setter Property="Template"> 47 <Setter.Value> 48 <ControlTemplate TargetType="{x:Type RepeatButton}"> 49 <Border x:Name="border" Background="{StaticResource ScrollBar.Static.Background}" BorderBrush="{StaticResource ScrollBar.Static.Border}" BorderThickness="1" SnapsToDevicePixels="true"> 50 <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 51 </Border> 52 <ControlTemplate.Triggers> 53 <Trigger Property="IsMouseOver" Value="true"> 54 <Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.MouseOver.Background}"/> 55 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.MouseOver.Border}"/> 56 </Trigger> 57 <Trigger Property="IsPressed" Value="true"> 58 <Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.Pressed.Background}"/> 59 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.Pressed.Border}"/> 60 </Trigger> 61 <Trigger Property="IsEnabled" Value="false"> 62 <Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/> 63 <Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.Disabled.Background}"/> 64 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.Disabled.Border}"/> 65 </Trigger> 66 </ControlTemplate.Triggers> 67 </ControlTemplate> 68 </Setter.Value> 69 </Setter> 70 </Style> 71 <Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}"> 72 <Setter Property="OverridesDefaultStyle" Value="true"/> 73 <Setter Property="IsTabStop" Value="false"/> 74 <Setter Property="Template"> 75 <Setter.Value> 76 <ControlTemplate TargetType="{x:Type Thumb}"> 77 <Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/> 78 <ControlTemplate.Triggers> 79 <Trigger Property="IsMouseOver" Value="true"> 80 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/> 81 </Trigger> 82 <Trigger Property="IsDragging" Value="true"> 83 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/> 84 </Trigger> 85 </ControlTemplate.Triggers> 86 </ControlTemplate> 87 </Setter.Value> 88 </Setter> 89 </Style> 90 <Style x:Key="ScrollBarThumbHorizontal" TargetType="{x:Type Thumb}"> 91 <Setter Property="OverridesDefaultStyle" Value="true"/> 92 <Setter Property="IsTabStop" Value="false"/> 93 <Setter Property="Template"> 94 <Setter.Value> 95 <ControlTemplate TargetType="{x:Type Thumb}"> 96 <Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/> 97 <ControlTemplate.Triggers> 98 <Trigger Property="IsMouseOver" Value="true"> 99 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/> 100 </Trigger> 101 <Trigger Property="IsDragging" Value="true"> 102 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/> 103 </Trigger> 104 </ControlTemplate.Triggers> 105 </ControlTemplate> 106 </Setter.Value> 107 </Setter> 108 </Style> 109 <Style x:Key="ScrollBarStyle1" TargetType="{x:Type ScrollBar}"> 110 <Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/> 111 <Setter Property="Stylus.IsFlicksEnabled" Value="false"/> 112 <Setter Property="Background" Value="{StaticResource ScrollBar.Static.Background}"/> 113 <Setter Property="BorderBrush" Value="{StaticResource ScrollBar.Static.Border}"/> 114 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 115 <Setter Property="BorderThickness" Value="1,0"/> 116 <Setter Property="Width" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/> 117 <Setter Property="MinWidth" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/> 118 <Setter Property="Template"> 119 <Setter.Value> 120 <ControlTemplate TargetType="{x:Type ScrollBar}"> 121 <Grid x:Name="Bg" SnapsToDevicePixels="true"> 122 <Grid.RowDefinitions> 123 <RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/> 124 <RowDefinition Height="0.00001*"/> 125 <RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/> 126 </Grid.RowDefinitions> 127 <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Grid.Row="1"/> 128 <RepeatButton x:Name="PART_LineUpButton" Command="{x:Static ScrollBar.LineUpCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}"> 129 <Path x:Name="ArrowTop" Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3,4,3,3" Stretch="Uniform"/> 130 </RepeatButton> 131 <Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1"> 132 <Track.DecreaseRepeatButton> 133 <RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 134 </Track.DecreaseRepeatButton> 135 <Track.IncreaseRepeatButton> 136 <RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 137 </Track.IncreaseRepeatButton> 138 <Track.Thumb> 139 <Thumb Style="{StaticResource ScrollBarThumbVertical}"/> 140 </Track.Thumb> 141 </Track> 142 <RepeatButton x:Name="PART_LineDownButton" Command="{x:Static ScrollBar.LineDownCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="2" Style="{StaticResource ScrollBarButton}"> 143 <Path x:Name="ArrowBottom" Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3,4,3,3" Stretch="Uniform"/> 144 </RepeatButton> 145 </Grid> 146 <ControlTemplate.Triggers> 147 <MultiDataTrigger> 148 <MultiDataTrigger.Conditions> 149 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineDownButton}" Value="true"/> 150 <Condition Binding="{Binding IsPressed, ElementName=PART_LineDownButton}" Value="true"/> 151 </MultiDataTrigger.Conditions> 152 <Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 153 </MultiDataTrigger> 154 <MultiDataTrigger> 155 <MultiDataTrigger.Conditions> 156 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineUpButton}" Value="true"/> 157 <Condition Binding="{Binding IsPressed, ElementName=PART_LineUpButton}" Value="true"/> 158 </MultiDataTrigger.Conditions> 159 <Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 160 </MultiDataTrigger> 161 <MultiDataTrigger> 162 <MultiDataTrigger.Conditions> 163 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineDownButton}" Value="true"/> 164 <Condition Binding="{Binding IsPressed, ElementName=PART_LineDownButton}" Value="false"/> 165 </MultiDataTrigger.Conditions> 166 <Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 167 </MultiDataTrigger> 168 <MultiDataTrigger> 169 <MultiDataTrigger.Conditions> 170 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineUpButton}" Value="true"/> 171 <Condition Binding="{Binding IsPressed, ElementName=PART_LineUpButton}" Value="false"/> 172 </MultiDataTrigger.Conditions> 173 <Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 174 </MultiDataTrigger> 175 <Trigger Property="IsEnabled" Value="false"> 176 <Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 177 <Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 178 </Trigger> 179 </ControlTemplate.Triggers> 180 </ControlTemplate> 181 </Setter.Value> 182 </Setter> 183 <Style.Triggers> 184 <Trigger Property="Orientation" Value="Horizontal"> 185 <Setter Property="Width" Value="Auto"/> 186 <Setter Property="MinWidth" Value="0"/> 187 <Setter Property="Height" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}"/> 188 <Setter Property="MinHeight" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}"/> 189 <Setter Property="BorderThickness" Value="0,1"/> 190 <Setter Property="Template"> 191 <Setter.Value> 192 <ControlTemplate TargetType="{x:Type ScrollBar}"> 193 <Grid x:Name="Bg" SnapsToDevicePixels="true"> 194 <Grid.ColumnDefinitions> 195 <ColumnDefinition MaxWidth="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/> 196 <ColumnDefinition Width="0.00001*"/> 197 <ColumnDefinition MaxWidth="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/> 198 </Grid.ColumnDefinitions> 199 <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="1"/> 200 <RepeatButton x:Name="PART_LineLeftButton" Command="{x:Static ScrollBar.LineLeftCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}"> 201 <Path x:Name="ArrowLeft" Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3" Stretch="Uniform"/> 202 </RepeatButton> 203 <Track x:Name="PART_Track" Grid.Column="1" IsEnabled="{TemplateBinding IsMouseOver}"> 204 <Track.DecreaseRepeatButton> 205 <RepeatButton Command="{x:Static ScrollBar.PageLeftCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 206 </Track.DecreaseRepeatButton> 207 <Track.IncreaseRepeatButton> 208 <RepeatButton Command="{x:Static ScrollBar.PageRightCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 209 </Track.IncreaseRepeatButton> 210 <Track.Thumb> 211 <Thumb Style="{StaticResource ScrollBarThumbHorizontal}"/> 212 </Track.Thumb> 213 </Track> 214 <RepeatButton x:Name="PART_LineRightButton" Command="{x:Static ScrollBar.LineRightCommand}" Grid.Column="2" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}"> 215 <Path x:Name="ArrowRight" Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3" Stretch="Uniform"/> 216 </RepeatButton> 217 </Grid> 218 <ControlTemplate.Triggers> 219 <MultiDataTrigger> 220 <MultiDataTrigger.Conditions> 221 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineRightButton}" Value="true"/> 222 <Condition Binding="{Binding IsPressed, ElementName=PART_LineRightButton}" Value="true"/> 223 </MultiDataTrigger.Conditions> 224 <Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 225 </MultiDataTrigger> 226 <MultiDataTrigger> 227 <MultiDataTrigger.Conditions> 228 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineLeftButton}" Value="true"/> 229 <Condition Binding="{Binding IsPressed, ElementName=PART_LineLeftButton}" Value="true"/> 230 </MultiDataTrigger.Conditions> 231 <Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 232 </MultiDataTrigger> 233 <MultiDataTrigger> 234 <MultiDataTrigger.Conditions> 235 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineRightButton}" Value="true"/> 236 <Condition Binding="{Binding IsPressed, ElementName=PART_LineRightButton}" Value="false"/> 237 </MultiDataTrigger.Conditions> 238 <Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 239 </MultiDataTrigger> 240 <MultiDataTrigger> 241 <MultiDataTrigger.Conditions> 242 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineLeftButton}" Value="true"/> 243 <Condition Binding="{Binding IsPressed, ElementName=PART_LineLeftButton}" Value="false"/> 244 </MultiDataTrigger.Conditions> 245 <Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 246 </MultiDataTrigger> 247 <Trigger Property="IsEnabled" Value="false"> 248 <Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 249 <Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 250 </Trigger> 251 </ControlTemplate.Triggers> 252 </ControlTemplate> 253 </Setter.Value> 254 </Setter> 255 </Trigger> 256 </Style.Triggers> 257 </Style>
拆解ScrollViewer的样式和模板
ScrollViewer的样式部分没有属性设置,我们直接看它的模板
1 <ControlTemplate TargetType="{x:Type ScrollViewer}"> 2 <!--使用Grid作为父级容器--> 3 <Grid x:Name="Grid" Background="{TemplateBinding Background}"> 4 <!--定义两行两列--> 5 <Grid.ColumnDefinitions> 6 <ColumnDefinition Width="*"/> 7 <ColumnDefinition Width="Auto"/> 8 </Grid.ColumnDefinitions> 9 <Grid.RowDefinitions> 10 <RowDefinition Height="*"/> 11 <RowDefinition Height="Auto"/> 12 </Grid.RowDefinitions> 13 <!--右下角的区域--> 14 <!--当垂直滚动条和水平滚动条同时显示时,右下角会出现一个矩形空白--> 15 <!--用于显示的一致性--> 16 <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/> 17 18 <!--用于内容显示--> 19 <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanHorizontallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" CanVerticallyScroll="False" Grid.Column="0" Content="{TemplateBinding Content}" CanContentScroll="{TemplateBinding CanContentScroll}" Margin="{TemplateBinding Padding}" Grid.Row="0"/> 20 21 <!--垂直滚动条--> 22 <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/> 23 24 <!--水平滚动条--> 25 <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/> 26 </Grid> 27 </ControlTemplate>
ScrollViewer的模板使用一个Grid控件作为父级容器,然后定义了两行两列。
它的模板组成结构如下图所示:

这里我们介绍一下ScrollContentPresenter类型。
ScrollContentPresenter 是 WPF 中 ScrollViewer 控件模板里的一个核心内部组件。
它的主要作用是承载并显示 ScrollViewer 的实际内容,同时负责处理滚动逻辑。可以把它理解为 ScrollViewer 的“内容展示与滚动引擎”。
ScrollContentPresenter继承自ContentPresenter类型,同时还实现了IScrollInfo接口
ContentPresenter用于内容显示,IScrollInfo接口用于支持滚动。
1 public sealed class ScrollContentPresenter : System.Windows.Controls.ContentPresenter, System.Windows.Controls.Primitives.IScrollInfo
关于ContentPresenter类型在前面的文章中我们进行过详细介绍
https://www.cnblogs.com/zhaotianff/p/22706548
下面我们介绍一下IScrollInfo接口
IScrollInfo接口表示 ScrollViewer 控件内部的主要可滚动区域。
IScrollInfo 接口提供了对启用子内容的逻辑滚动的方法的访问,以及计算哪些子元素可见。
此外,它还定义用于定义可滚动区域的属性。例如:
-
CanHorizontallyScroll/CanVerticallyScroll:这两个属性分别指示是否允许在水平/垂直方向上进行滚动。 -
HorizontalOffset/VerticalOffset:获取或设置内容在水平/垂直方向上的滚动偏移量。 -
ExtentHeight/ExtentWidth:获取内容的总高度/宽度。 -
ViewportHeight/ViewportWidth:获取当前可视区域的高度/宽度。 -
ScrollOwner:获取或设置控制此ScrollContentPresenter滚动行为的ScrollViewer元素。
IScrollInfo接口提供了一系列控制滚动的方法,例如:
-
LineUp(),LineDown(),LineLeft(),LineRight():按行滚动。 -
PageUp(),PageDown(),PageLeft(),PageRight():按页滚动。 -
MouseWheelUp(),MouseWheelDown():响应鼠标滚轮。
-
SetHorizontalOffset(double),SetVerticalOffset(double):直接设置滚动偏移量。 -
MakeVisible(Visual, Rect):尝试将指定元素滚动到可视区域内。
总结来说:ScrollViewer 通过模板中的水平/垂直 ScrollBar 接收用户的滚动输入,然后由 ScrollViewer 把滚动请求转发给实现了 IScrollInfo 的滚动逻辑组件(ScrollContentPresenter),从而实现滚动。此外,ScrollBar 还负责显示当前滚动状态。
所以在我们自定义模板时,所有元素的名称是要保持跟系统定义的一致:
1、必须包含一个类型为 ScrollContentPresenter 且 Name 为 PART_ScrollContentPresenter 的元素。
2、必须包含两个类型为 ScrollBar 且 Name分别为PART_VerticalScrollBar 和 PART_HorizontalScrollBar的元素。
注意:如果元素名称不一致,滚动功能就会异常。
1 <!--用于内容显示--> 2 <ScrollContentPresenter x:Name="PART_ScrollContentPresenter"/> 3 4 <!--垂直滚动条--> 5 <ScrollBar x:Name="PART_VerticalScrollBar"/> 6 7 <!--水平滚动条--> 8 <ScrollBar x:Name="PART_HorizontalScrollBar"/>
它的内部原理如下:
ScrollViewer 在应用模板时,会在可视化树中查找名为 PART_ScrollContentPresenter 的元素,并将其与自身关联。
然后,ScrollViewer 的滚动条(PART_VerticalScrollBar 和 PART_HorizontalScrollBar)会与这个 ScrollContentPresenter 的 IScrollInfo 属性进行绑定,实现同步。
拆解ScrollBar的样式和模板
1、首先是Tab 键切换控件焦点时看到的虚线焦点框样式,如下所示
1 <Style x:Key="FocusVisual"> 2 <Setter Property="Control.Template"> 3 <Setter.Value> 4 <ControlTemplate> 5 <Rectangle Margin="2" StrokeDashArray="1 2" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" SnapsToDevicePixels="true" StrokeThickness="1"/> 6 </ControlTemplate> 7 </Setter.Value> 8 </Setter> 9 </Style>
2、资源画刷定义
1 <SolidColorBrush x:Key="ScrollBar.Static.Background" Color="#F0F0F0"/> 2 <SolidColorBrush x:Key="ScrollBar.Static.Border" Color="#F0F0F0"/> 3 <SolidColorBrush x:Key="ScrollBar.Static.Glyph" Color="#606060"/> 4 <SolidColorBrush x:Key="ScrollBar.Static.Thumb" Color="#CDCDCD"/> 5 <SolidColorBrush x:Key="ScrollBar.MouseOver.Background" Color="#DADADA"/> 6 <SolidColorBrush x:Key="ScrollBar.MouseOver.Border" Color="#DADADA"/> 7 <SolidColorBrush x:Key="ScrollBar.MouseOver.Glyph" Color="#000000"/> 8 <SolidColorBrush x:Key="ScrollBar.MouseOver.Thumb" Color="#A6A6A6"/> 9 <SolidColorBrush x:Key="ScrollBar.Pressed.Background" Color="#606060"/> 10 <SolidColorBrush x:Key="ScrollBar.Pressed.Border" Color="#606060"/> 11 <SolidColorBrush x:Key="ScrollBar.Pressed.Thumb" Color="#606060"/> 12 <SolidColorBrush x:Key="ScrollBar.Pressed.Glyph" Color="#FFFFFF"/> 13 <SolidColorBrush x:Key="ScrollBar.Disabled.Background" Color="#F0F0F0"/> 14 <SolidColorBrush x:Key="ScrollBar.Disabled.Border" Color="#F0F0F0"/> 15 <SolidColorBrush x:Key="ScrollBar.Disabled.Glyph" Color="#BFBFBF"/>
3、滚动条透明区域样式
1 <Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}"> 2 <Setter Property="OverridesDefaultStyle" Value="true"/> 3 <Setter Property="Background" Value="Transparent"/> 4 <Setter Property="Focusable" Value="false"/> 5 <Setter Property="IsTabStop" Value="false"/> 6 <Setter Property="Template"> 7 <Setter.Value> 8 <ControlTemplate TargetType="{x:Type RepeatButton}"> 9 <Rectangle Fill="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"/> 10 </ControlTemplate> 11 </Setter.Value> 12 </Setter> 13 </Style>
先用一张图展示一下这个样式具体是对应到哪个控件。(方便演示,使用了红色和绿色来区分)

正常一个滚动条区域,会由一个滑块和滑轨组成,滑块上面的区域是一个RepeatButton,滑块下面的区域也是一个RepeatButton。
这两个RepeatButton使用的样式就是上面提到的滚动条透明区域样式
防止有些小伙伴不熟悉RepeatButton,这里把RepeatButton拿出来快速讲解一下。
RepeatButton也是一个按钮控件,它类似于Button。但是,RepeatButton 元素可让你控制 Click 事件发生的时间和方式。
通俗点来说就是:Button 是普通单击按钮,点一次触发一次 Click;RepeatButton 是“按住会重复触发”的按钮,按住期间会不断触发 Click/Command,直到松开。
RepeatButton 有两个关键属性:
-
Delay:按下后等待多久开始重复,单位毫秒,默认通常为500ms -
Interval:开始重复后,每次触发之间的间隔,单位毫秒,默认通常为33ms
ScrollBar这里之所以使用RepeatButton,是因为滚动条空白区域是可以通过按住不放来实现持续滚动的。

4、两个箭头按钮样式
1 <!--ScrollBar的两个箭头按钮样式--> 2 <Style x:Key="ScrollBarButton" TargetType="{x:Type RepeatButton}"> 3 <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> 4 <Setter Property="BorderThickness" Value="1"/> 5 <Setter Property="HorizontalContentAlignment" Value="Center"/> 6 <Setter Property="VerticalContentAlignment" Value="Center"/> 7 <Setter Property="Padding" Value="1"/> 8 <Setter Property="Focusable" Value="false"/> 9 <Setter Property="IsTabStop" Value="false"/> 10 <Setter Property="Template"> 11 <Setter.Value> 12 <ControlTemplate TargetType="{x:Type RepeatButton}"> 13 <Border x:Name="border" Background="{StaticResource ScrollBar.Static.Background}" BorderBrush="{StaticResource ScrollBar.Static.Border}" BorderThickness="1" SnapsToDevicePixels="true"> 14 <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 15 </Border> 16 <ControlTemplate.Triggers> 17 <Trigger Property="IsMouseOver" Value="true"> 18 <Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.MouseOver.Background}"/> 19 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.MouseOver.Border}"/> 20 </Trigger> 21 <Trigger Property="IsPressed" Value="true"> 22 <Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.Pressed.Background}"/> 23 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.Pressed.Border}"/> 24 </Trigger> 25 <Trigger Property="IsEnabled" Value="false"> 26 <Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/> 27 <Setter Property="Background" TargetName="border" Value="{StaticResource ScrollBar.Disabled.Background}"/> 28 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ScrollBar.Disabled.Border}"/> 29 </Trigger> 30 </ControlTemplate.Triggers> 31 </ControlTemplate> 32 </Setter.Value> 33 </Setter> 34 </Style>
通过下面的示意图可以看到,这个RepeatButton样式,对应的是顶部/底部的两个小箭头按钮样式。
需要注意的是:这里暂时只设置了样式,并没有设置按钮的内容。按钮的内容会在接下来的样式讲解中说明。

5、滑块的样式
垂直滑块的样式
1 <!--垂直滑块的样式--> 2 <Style x:Key="ScrollBarThumbVertical" TargetType="{x:Type Thumb}"> 3 <Setter Property="OverridesDefaultStyle" Value="true"/> 4 <Setter Property="IsTabStop" Value="false"/> 5 <Setter Property="Template"> 6 <Setter.Value> 7 <ControlTemplate TargetType="{x:Type Thumb}"> 8 <!--通过Rectangle来实现一个滑块--> 9 <Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/> 10 <ControlTemplate.Triggers> 11 <!--鼠标悬停时--> 12 <Trigger Property="IsMouseOver" Value="true"> 13 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/> 14 </Trigger> 15 <!--拖动时--> 16 <Trigger Property="IsDragging" Value="true"> 17 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/> 18 </Trigger> 19 </ControlTemplate.Triggers> 20 </ControlTemplate> 21 </Setter.Value> 22 </Setter> 23 </Style>
水平滑块的样式
1 <!--水平滑块的样式--> 2 <Style x:Key="ScrollBarThumbHorizontal" TargetType="{x:Type Thumb}"> 3 <Setter Property="OverridesDefaultStyle" Value="true"/> 4 <Setter Property="IsTabStop" Value="false"/> 5 <Setter Property="Template"> 6 <Setter.Value> 7 <ControlTemplate TargetType="{x:Type Thumb}"> 8 <!--通过Rectangle来实现一个滑块--> 9 <Rectangle x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" Height="{TemplateBinding Height}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}"/> 10 <ControlTemplate.Triggers> 11 <!--鼠标悬停时--> 12 <Trigger Property="IsMouseOver" Value="true"> 13 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.MouseOver.Thumb}"/> 14 </Trigger> 15 <!--拖动时--> 16 <Trigger Property="IsDragging" Value="true"> 17 <Setter Property="Fill" TargetName="rectangle" Value="{StaticResource ScrollBar.Pressed.Thumb}"/> 18 </Trigger> 19 </ControlTemplate.Triggers> 20 </ControlTemplate> 21 </Setter.Value> 22 </Setter> 23 </Style>
滑块示意如下

这里是通过两个Rectangle来实现,并通过触发器来实现鼠标悬停和拖动时的效果。
我们可以尝试把滑块改成一个椭圆
1 <Ellipse x:Name="rectangle" Fill="{StaticResource ScrollBar.Static.Thumb}" SnapsToDevicePixels="True" Width="{TemplateBinding Width}" Height="{TemplateBinding ActualWidth}"/>
效果如下:

6、Track控件
在介绍ScrollBar样式前,我们还需要了解一下Track这个控件。
因为Track控件是ScrollBar控件的组成部分之一。
Track控件是一个基元控件(control primitive),该控件负责处理Thumb控件与两个RepeatButton控件的位置摆放和尺寸大小,而这几个控件用于设置一个Value
可以通过下面的图来直观感受一下

这里所说的Control Primitive,是指这个控件一般并不会在业务界面使用,仅在控件内部使用,例如Slider控件和ScrollBar控件。
Track控件没有默认的控件模板,它的控件模板是通过ScrollBar和Slider控件模板来实现的。
Track由Thumb控件和两个RepeatButton组成,所以我们可以单独设置Thumb和RepeatButton的样式。
1 <Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1"> 2 <!--DecreaseRepeatButton是Track的一个属性--> 3 <!--设置用于减小 Track 控件 Value 属性的 RepeatButton--> 4 <Track.DecreaseRepeatButton> 5 <RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 6 </Track.DecreaseRepeatButton> 7 <!--IncreaseRepeatButton是Track的一个属性--> 8 <!--设置用于增加 Track 控件 Value 属性的 RepeatButton--> 9 <Track.IncreaseRepeatButton> 10 <RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 11 </Track.IncreaseRepeatButton> 12 <!--Thumb是Track的一个属性--> 13 <!--设置用于更改 Track 控件 Value 属性的 Thumb--> 14 <Track.Thumb> 15 <Thumb Style="{StaticResource ScrollBarThumbVertical}"/> 16 </Track.Thumb> 17 </Track>
此外,我们还需要了解一下Track.Orientation这个依赖属性,这个属性可以获取或设置Track是水平显示还是垂直显示。
ScrollBar的Orientation属性就是绑定到了Track.Orientation属性。
我们在设置ScrollBar的水平或垂直滚动条样式时就需要用到这个属性。
1 <Style x:Key="ScrollBarStyle1" TargetType="{x:Type ScrollBar}"> 2 <!--默认是设置垂直状态ScrollBar样式--> 3 <Setter Property="Template"> 4 <Setter.Value> 5 <ControlTemplate TargetType="{x:Type ScrollBar}"> 6 <Grid x:Name="Bg" SnapsToDevicePixels="true"> 7 ...... 8 </Grid> 9 </ControlTemplate> 10 </Setter.Value> 11 </Setter> 12 <Style.Triggers> 13 <!--通过触发器设置水平状态--> 14 <Trigger Property="Orientation" Value="Horizontal"> 15 <Setter Property="Template"> 16 <Setter.Value> 17 <ControlTemplate TargetType="{x:Type ScrollBar}"> 18 <Grid x:Name="Bg" SnapsToDevicePixels="true"> 19 ...... 20 </Grid> 21 </ControlTemplate> 22 </Setter.Value> 23 </Setter> 24 </Trigger> 25 </Style.Triggers> 26 </Style>
更详细的Track控件介绍可以参考官网的文档链接
7、垂直ScrollBar样式和模板
前面已经把ScrollBar子元素的样式介绍完了,现在正式进入ScrollBar样式的讲解。
ScrollBar样式分为水平和垂直两种样式。
我们先介绍一下ScrollBar的垂直样式和模板。
1 <Style x:Key="ScrollBarStyle1" TargetType="{x:Type ScrollBar}"> 2 <Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/> 3 <Setter Property="Stylus.IsFlicksEnabled" Value="false"/> 4 <Setter Property="Background" Value="{StaticResource ScrollBar.Static.Background}"/> 5 <Setter Property="BorderBrush" Value="{StaticResource ScrollBar.Static.Border}"/> 6 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 7 <Setter Property="BorderThickness" Value="1,0"/> 8 <Setter Property="Width" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/> 9 <Setter Property="MinWidth" Value="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/> 10 <Setter Property="Template"> 11 <Setter.Value> 12 <ControlTemplate TargetType="{x:Type ScrollBar}"> 13 <!--使用Grid定义三行--> 14 <Grid x:Name="Bg" SnapsToDevicePixels="true"> 15 <Grid.RowDefinitions> 16 <!--第一行用于显示鼠标按住时向上滚动的RepeatButton--> 17 <RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/> 18 <!--第二行用于显示滑块区域--> 19 <RowDefinition Height="0.00001*"/> 20 <!--第三行用于显示鼠标按住时向下滚动的RepeatButton--> 21 <RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/> 22 </Grid.RowDefinitions> 23 <!--滚动区域背景--> 24 <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Grid.Row="1"/> 25 <RepeatButton x:Name="PART_LineUpButton" Command="{x:Static ScrollBar.LineUpCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}"> 26 <!--向上的箭头图形--> 27 <Path x:Name="ArrowTop" Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3,4,3,3" Stretch="Uniform"/> 28 </RepeatButton> 29 <Track x:Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="1"> 30 <!--DecreaseRepeatButton是Track的一个属性--> 31 <!--设置用于减小 Track 控件 Value 属性的 RepeatButton--> 32 <Track.DecreaseRepeatButton> 33 <RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 34 </Track.DecreaseRepeatButton> 35 <!--IncreaseRepeatButton是Track的一个属性--> 36 <!--设置用于增加 Track 控件 Value 属性的 RepeatButton--> 37 <Track.IncreaseRepeatButton> 38 <RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 39 </Track.IncreaseRepeatButton> 40 <!--Thumb是Track的一个属性--> 41 <!--设置用于更改 Track 控件 Value 属性的 Thumb--> 42 <Track.Thumb> 43 <Thumb Style="{StaticResource ScrollBarThumbVertical}"/> 44 </Track.Thumb> 45 </Track> 46 <RepeatButton x:Name="PART_LineDownButton" Command="{x:Static ScrollBar.LineDownCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Grid.Row="2" Style="{StaticResource ScrollBarButton}"> 47 <!--向下的箭头图形--> 48 <Path x:Name="ArrowBottom" Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3,4,3,3" Stretch="Uniform"/> 49 </RepeatButton> 50 </Grid> 51 </ControlTemplate > 52 </Setter.Value> 53 </Setter> 54 </Style>
关于属性设置这部分,这里不做详细介绍,基本上都是控件常见的一些属性。
模板部分,这里是通过Grid定义了三行,第一行显示向上的箭头按钮,第二行显示滚动区域及滚动滑块,第三行显示向下的箭头按钮。
滚动区域是通过一个Border来实现,滚动功能是通过Track控件来实现,在前面已经介绍过了。
接下来是模板的触发器部分,这里主要是控件向上/向下箭头的一些显示效果,可以就不做详细介绍了。
自己在设计样式时,直接参考下面的模式,设置不同的配色即可。
1 <ControlTemplate.Triggers> 2 <MultiDataTrigger> 3 <MultiDataTrigger.Conditions> 4 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineDownButton}" Value="true"/> 5 <Condition Binding="{Binding IsPressed, ElementName=PART_LineDownButton}" Value="true"/> 6 </MultiDataTrigger.Conditions> 7 <Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 8 </MultiDataTrigger> 9 <MultiDataTrigger> 10 <MultiDataTrigger.Conditions> 11 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineUpButton}" Value="true"/> 12 <Condition Binding="{Binding IsPressed, ElementName=PART_LineUpButton}" Value="true"/> 13 </MultiDataTrigger.Conditions> 14 <Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 15 </MultiDataTrigger> 16 <MultiDataTrigger> 17 <MultiDataTrigger.Conditions> 18 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineDownButton}" Value="true"/> 19 <Condition Binding="{Binding IsPressed, ElementName=PART_LineDownButton}" Value="false"/> 20 </MultiDataTrigger.Conditions> 21 <Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 22 </MultiDataTrigger> 23 <MultiDataTrigger> 24 <MultiDataTrigger.Conditions> 25 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineUpButton}" Value="true"/> 26 <Condition Binding="{Binding IsPressed, ElementName=PART_LineUpButton}" Value="false"/> 27 </MultiDataTrigger.Conditions> 28 <Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 29 </MultiDataTrigger> 30 <Trigger Property="IsEnabled" Value="false"> 31 <Setter Property="Fill" TargetName="ArrowTop" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 32 <Setter Property="Fill" TargetName="ArrowBottom" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 33 </Trigger> 34 </ControlTemplate.Triggers>
接下来我们可以看到ScrollBar的水平样式和模板,ScrollBar的水平样式切换是通过Orientation属性来触发,这个属性也在前面介绍过了。
1 <Style.Triggers> 2 <!--水平ScrollBar--> 3 <Trigger Property="Orientation" Value="Horizontal"> 4 <Setter Property="Width" Value="Auto"/> 5 <Setter Property="MinWidth" Value="0"/> 6 <Setter Property="Height" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}"/> 7 <Setter Property="MinHeight" Value="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarHeightKey}}"/> 8 <Setter Property="BorderThickness" Value="0,1"/> 9 <Setter Property="Template"> 10 <Setter.Value> 11 <ControlTemplate TargetType="{x:Type ScrollBar}"> 12 <!--使用Grid定义三列--> 13 <Grid x:Name="Bg" SnapsToDevicePixels="true"> 14 <Grid.ColumnDefinitions> 15 <!--第一列用于显示鼠标按住时向左滚动的RepeatButton--> 16 <ColumnDefinition MaxWidth="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/> 17 <!--第二列用于显示滚动区域--> 18 <ColumnDefinition Width="0.00001*"/> 19 <!--第三列用于显示鼠标按住时向右滚动的RepeatButton--> 20 <ColumnDefinition MaxWidth="{DynamicResource {x:Static SystemParameters.HorizontalScrollBarButtonWidthKey}}"/> 21 </Grid.ColumnDefinitions> 22 <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="1"/> 23 <RepeatButton x:Name="PART_LineLeftButton" Command="{x:Static ScrollBar.LineLeftCommand}" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}"> 24 <Path x:Name="ArrowLeft" Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3" Stretch="Uniform"/> 25 </RepeatButton> 26 <Track x:Name="PART_Track" Grid.Column="1" IsEnabled="{TemplateBinding IsMouseOver}"> 27 <Track.DecreaseRepeatButton> 28 <RepeatButton Command="{x:Static ScrollBar.PageLeftCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 29 </Track.DecreaseRepeatButton> 30 <Track.IncreaseRepeatButton> 31 <RepeatButton Command="{x:Static ScrollBar.PageRightCommand}" Style="{StaticResource RepeatButtonTransparent}"/> 32 </Track.IncreaseRepeatButton> 33 <Track.Thumb> 34 <Thumb Style="{StaticResource ScrollBarThumbHorizontal}"/> 35 </Track.Thumb> 36 </Track> 37 <RepeatButton x:Name="PART_LineRightButton" Command="{x:Static ScrollBar.LineRightCommand}" Grid.Column="2" IsEnabled="{TemplateBinding IsMouseOver}" Style="{StaticResource ScrollBarButton}"> 38 <Path x:Name="ArrowRight" Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z" Fill="{StaticResource ScrollBar.Static.Glyph}" Margin="3" Stretch="Uniform"/> 39 </RepeatButton> 40 </Grid> 41 <ControlTemplate.Triggers> 42 <MultiDataTrigger> 43 <MultiDataTrigger.Conditions> 44 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineRightButton}" Value="true"/> 45 <Condition Binding="{Binding IsPressed, ElementName=PART_LineRightButton}" Value="true"/> 46 </MultiDataTrigger.Conditions> 47 <Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 48 </MultiDataTrigger> 49 <MultiDataTrigger> 50 <MultiDataTrigger.Conditions> 51 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineLeftButton}" Value="true"/> 52 <Condition Binding="{Binding IsPressed, ElementName=PART_LineLeftButton}" Value="true"/> 53 </MultiDataTrigger.Conditions> 54 <Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.Pressed.Glyph}"/> 55 </MultiDataTrigger> 56 <MultiDataTrigger> 57 <MultiDataTrigger.Conditions> 58 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineRightButton}" Value="true"/> 59 <Condition Binding="{Binding IsPressed, ElementName=PART_LineRightButton}" Value="false"/> 60 </MultiDataTrigger.Conditions> 61 <Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 62 </MultiDataTrigger> 63 <MultiDataTrigger> 64 <MultiDataTrigger.Conditions> 65 <Condition Binding="{Binding IsMouseOver, ElementName=PART_LineLeftButton}" Value="true"/> 66 <Condition Binding="{Binding IsPressed, ElementName=PART_LineLeftButton}" Value="false"/> 67 </MultiDataTrigger.Conditions> 68 <Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.MouseOver.Glyph}"/> 69 </MultiDataTrigger> 70 <Trigger Property="IsEnabled" Value="false"> 71 <Setter Property="Fill" TargetName="ArrowLeft" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 72 <Setter Property="Fill" TargetName="ArrowRight" Value="{StaticResource ScrollBar.Disabled.Glyph}"/> 73 </Trigger> 74 </ControlTemplate.Triggers> 75 </ControlTemplate> 76 </Setter.Value> 77 </Setter> 78 </Trigger> 79 </Style.Triggers>
ScrollBar的水平样式只是将Grid的定义由行转换为列,其它逻辑基本上是一样的,这里就不再赘叙了。
VisualState
| VisualState 名称 | VisualStateGroup 名称 | Description |
|---|---|---|
| InvalidFocused | ValidationStates | Validation.HasError附加属性是true,且控件具有焦点。 |
| InvalidUnfocused | ValidationStates | Validation.HasError附加属性是true,且控件没有焦点。 |
| Valid | ValidationStates | 该控件使用 Validation 类, Validation.HasError 附加属性为 false. |
这几个VisualState对于我们自定义样式来说,基本上用处不大,所以这里就不做详细介绍了。
因为这几个VisualState都是跟验证相关的,正常来说,ScrollViewer也不会去做这样一个操作。
ScrollViewer样式示例
我们先定义滚动条透明区域样式
1 <!--滚动条透明区域样式--> 2 <Style x:Key="WD.RepeatButtonTransparent" TargetType="{x:Type RepeatButton}"> 3 <Setter Property="OverridesDefaultStyle" Value="True" /> 4 <Setter Property="Background" Value="Transparent" /> 5 <Setter Property="Focusable" Value="False" /> 6 <Setter Property="IsTabStop" Value="False" /> 7 <Setter Property="SnapsToDevicePixels" Value="True" /> 8 <Setter Property="UseLayoutRounding" Value="True" /> 9 <Setter Property="Template"> 10 <Setter.Value> 11 <ControlTemplate TargetType="{x:Type RepeatButton}"> 12 <Rectangle Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Background}" /> 13 </ControlTemplate> 14 </Setter.Value> 15 </Setter> 16 </Style>
然后再定义垂直/水平滚动滑块样式
这里主要是控制好Rectangle的显示效果即可
1 <!--垂直滚动滑块样式--> 2 <Style x:Key="WD.ScrollBarThumbVertical" TargetType="{x:Type Thumb}"> 3 <Setter Property="OverridesDefaultStyle" Value="True" /> 4 <Setter Property="Background" Value="Transparent" /> 5 <Setter Property="Focusable" Value="False" /> 6 <Setter Property="IsTabStop" Value="False" /> 7 <Setter Property="SnapsToDevicePixels" Value="True" /> 8 <Setter Property="UseLayoutRounding" Value="True" /> 9 <Setter Property="Template"> 10 <Setter.Value> 11 <ControlTemplate TargetType="{x:Type Thumb}"> 12 <Border x:Name="PART_Thumbborder"> 13 <Rectangle x:Name="PART_Rectangle" Height="{TemplateBinding Height}" Fill="{DynamicResource WD.RegularTextBrush}" RadiusX="1" RadiusY="1" /> 14 </Border> 15 </ControlTemplate> 16 </Setter.Value> 17 </Setter> 18 </Style> 19 20 <!--水平滚动滑块样式--> 21 <Style x:Key="WD.ScrollBarThumbHorizontal" TargetType="{x:Type Thumb}"> 22 <Setter Property="OverridesDefaultStyle" Value="True" /> 23 <Setter Property="Background" Value="Transparent" /> 24 <Setter Property="Focusable" Value="False" /> 25 <Setter Property="IsTabStop" Value="False" /> 26 <Setter Property="SnapsToDevicePixels" Value="True" /> 27 <Setter Property="UseLayoutRounding" Value="True" /> 28 <Setter Property="Template"> 29 <Setter.Value> 30 <ControlTemplate TargetType="{x:Type Thumb}"> 31 <Border x:Name="PART_Thumbborder"> 32 <Rectangle x:Name="PART_Rectangle" Height="{TemplateBinding Height}" Fill="{DynamicResource WD.PrimaryTextBrush}" RadiusX="1" RadiusY="1" /> 33 </Border> 34 </ControlTemplate> 35 </Setter.Value> 36 </Setter> 37 </Style>
然后再定义ScrollBar样式,包括水平和垂直的效果
1 <Style x:Key="WD.DefaultScrollBar" TargetType="{x:Type ScrollBar}"> 2 <!--为滑块增加动画效果,当鼠标进入时,滑块变宽--> 3 <Style.Resources> 4 <Storyboard x:Key="ScrollMouseOut"> 5 <DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Width" To="2" Duration="00:00:0.1" /> 6 </Storyboard> 7 <Storyboard x:Key="ScrollMouseOver"> 8 <DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Width" To="10" Duration="00:00:0.1" /> 9 </Storyboard> 10 </Style.Resources> 11 <Setter Property="Stylus.IsPressAndHoldEnabled" Value="False" /> 12 <Setter Property="Stylus.IsFlicksEnabled" Value="False" /> 13 <Setter Property="BorderThickness" Value="0" /> 14 <Setter Property="Width" Value="12" /> 15 <Setter Property="MinWidth" Value="8" /> 16 <Setter Property="SnapsToDevicePixels" Value="True" /> 17 <Setter Property="UseLayoutRounding" Value="True" /> 18 <Setter Property="FrameworkElement.OverridesDefaultStyle" Value="True" /> 19 <Setter Property="Template"> 20 <Setter.Value> 21 <ControlTemplate TargetType="{x:Type ScrollBar}"> 22 <!--这里省略了向上/向下箭头按钮--> 23 <!--只保留了中间的滚动区域--> 24 <!--使用Grid作为容器--> 25 <Grid x:Name="Bg" Width="{TemplateBinding Width}" Background="Transparent" SnapsToDevicePixels="True"> 26 <Track x:Name="PART_Track" Width="2" Margin="0,0,1,0" HorizontalAlignment="Right" IsDirectionReversed="True" IsEnabled="{TemplateBinding IsMouseOver}"> 27 <Track.DecreaseRepeatButton> 28 <RepeatButton Command="{x:Static ScrollBar.PageUpCommand}" Style="{StaticResource WD.RepeatButtonTransparent}" /> 29 </Track.DecreaseRepeatButton> 30 <Track.IncreaseRepeatButton> 31 <RepeatButton Command="{x:Static ScrollBar.PageDownCommand}" Style="{StaticResource WD.RepeatButtonTransparent}" /> 32 </Track.IncreaseRepeatButton> 33 <Track.Thumb> 34 <Thumb Style="{StaticResource WD.ScrollBarThumbVertical}" /> 35 </Track.Thumb> 36 </Track> 37 </Grid> 38 <!--鼠标划过时,触发滚动条变宽动画--> 39 <ControlTemplate.Triggers> 40 <Trigger Property="IsMouseOver" Value="True"> 41 <Trigger.ExitActions> 42 <BeginStoryboard Storyboard="{StaticResource ScrollMouseOut}" /> 43 </Trigger.ExitActions> 44 <Trigger.EnterActions> 45 <BeginStoryboard Storyboard="{StaticResource ScrollMouseOver}" /> 46 </Trigger.EnterActions> 47 </Trigger> 48 </ControlTemplate.Triggers> 49 </ControlTemplate> 50 </Setter.Value> 51 </Setter> 52 <Style.Triggers> 53 <Trigger Property="Orientation" Value="Horizontal"> 54 <Setter Property="Width" Value="Auto" /> 55 <Setter Property="MinWidth" Value="0" /> 56 <Setter Property="Height" Value="12" /> 57 <Setter Property="MinHeight" Value="8" /> 58 <Setter Property="Template"> 59 <Setter.Value> 60 <ControlTemplate TargetType="{x:Type ScrollBar}"> 61 <!--为滑块增加动画效果,当鼠标进入时,滑块变高--> 62 <ControlTemplate.Resources> 63 <Storyboard x:Key="ScrollMouseOutHeight"> 64 <DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Height" To="2" Duration="00:00:0.1" /> 65 </Storyboard> 66 <Storyboard x:Key="ScrollMouseOverHeight"> 67 <DoubleAnimation Storyboard.TargetName="PART_Track" Storyboard.TargetProperty="Height" To="10" Duration="00:00:0.1" /> 68 </Storyboard> 69 </ControlTemplate.Resources> 70 <Grid Height="{TemplateBinding Height}" Background="Transparent" SnapsToDevicePixels="True"> 71 <Track x:Name="PART_Track" Height="2" Margin="0,0,0,1" VerticalAlignment="Bottom" IsEnabled="{TemplateBinding IsMouseOver}" Visibility="Hidden"> 72 <Track.DecreaseRepeatButton> 73 <RepeatButton Command="{x:Static ScrollBar.PageLeftCommand}" Style="{StaticResource WD.RepeatButtonTransparent}" /> 74 </Track.DecreaseRepeatButton> 75 <Track.IncreaseRepeatButton> 76 <RepeatButton Command="{x:Static ScrollBar.PageRightCommand}" Style="{StaticResource WD.RepeatButtonTransparent}" /> 77 </Track.IncreaseRepeatButton> 78 <Track.Thumb> 79 <Thumb Style="{StaticResource WD.ScrollBarThumbHorizontal}" /> 80 </Track.Thumb> 81 </Track> 82 </Grid> 83 <ControlTemplate.Triggers> 84 <Trigger Property="IsMouseOver" Value="True"> 85 <Trigger.ExitActions> 86 <BeginStoryboard Storyboard="{StaticResource ScrollMouseOutHeight}" /> 87 </Trigger.ExitActions> 88 <Trigger.EnterActions> 89 <BeginStoryboard Storyboard="{StaticResource ScrollMouseOverHeight}" /> 90 </Trigger.EnterActions> 91 </Trigger> 92 </ControlTemplate.Triggers> 93 </ControlTemplate> 94 </Setter.Value> 95 </Setter> 96 </Trigger> 97 </Style.Triggers> 98 </Style>
最后再定义ScrollViewer样式
1 <Style x:Key="WD.DefaultScrollViewer" TargetType="{x:Type ScrollViewer}"> 2 <Setter Property="SnapsToDevicePixels" Value="True" /> 3 <Setter Property="UseLayoutRounding" Value="True" /> 4 <Setter Property="FrameworkElement.OverridesDefaultStyle" Value="True" /> 5 <Setter Property="Template"> 6 <Setter.Value> 7 <ControlTemplate TargetType="{x:Type ScrollViewer}"> 8 <Grid Background="{TemplateBinding Background}"> 9 <Grid.ColumnDefinitions> 10 <ColumnDefinition Width="*" /> 11 <ColumnDefinition Width="Auto" /> 12 </Grid.ColumnDefinitions> 13 <Grid.RowDefinitions> 14 <RowDefinition Height="*" /> 15 <RowDefinition Height="Auto" /> 16 </Grid.RowDefinitions> 17 <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="{TemplateBinding Padding}" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /> 18 <!--使用自定义样式的ScrollBar(垂直)--> 19 <ScrollBar x:Name="PART_VerticalScrollBar" Grid.Row="0" Grid.Column="1" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource WD.DefaultScrollBar}"/> 20 <!--使用自定义样式的ScrollBar(水平)--> 21 <!--主要是通过设置属性 Orientation="Horizontal"来进行切换--> 22 <ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource WD.DefaultScrollBar}" /> 23 </Grid> 24 </ControlTemplate> 25 </Setter.Value> 26 </Setter> 27 </Style>
运行效果

示例代码
https://github.com/zhaotianff/cnblog-demo-code/tree/main/WPFStyleAndTemplateDemo

浙公网安备 33010602011771号