2021/04/10

ストライプ付きプログレスバー

プログレスバーの最近の流行りは、コンテンツを邪魔しないように細くして、存在感を主張しないものだと思いますが、控えめにアクセントを付ける方法として、ストライプを付けるものがあります。それで、進行中はストライプをアニメーションさせると。

WPFというかXAMLのアニメーションはその辺柔軟なので、それほど難しくはないです。
<SolidColorBrush x:Key="ProgressBar.Background" Color="#FFE6E6E6"/>
<SolidColorBrush x:Key="ProgressBar.Progress" Color="#FF1C61F3"/>
<SolidColorBrush x:Key="ProgressBar.Stripe" Color="#33FFFFFF"/>
<Style x:Key="StripedProgressBarStyle" TargetType="{x:Type ProgressBar}">
<Setter Property="Background" Value="{StaticResource ProgressBar.Background}"/>
<Setter Property="Foreground" Value="{StaticResource ProgressBar.Progress}"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Determinate">
<Storyboard RepeatBehavior="Forever">
<RectAnimation Storyboard.TargetName="Stripe"
Storyboard.TargetProperty="(Shape.Fill).(TileBrush.Viewport)"
Duration="0:0:1"
From="0,0,20,20" To="-20,0,20,20"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Indeterminate"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="{TemplateBinding Background}"/>
<Rectangle x:Name="PART_Track"/>
<Grid x:Name="PART_Indicator"
ClipToBounds="true"
HorizontalAlignment="Left">
<Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}"/>
<Rectangle x:Name="Stripe">
<Rectangle.Fill>
<DrawingBrush TileMode="Tile" Stretch="Uniform"
Viewport="0,0,20,20" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="{StaticResource ProgressBar.Stripe}">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,0">
<LineSegment Point="5,0"/>
<LineSegment Point="10,5"/>
<LineSegment Point="10,10"/>
</PathFigure>
<PathFigure StartPoint="0,5">
<LineSegment Point="5,10"/>
<LineSegment Point="0,10"/>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Value" Value="100">
<Setter TargetName="Stripe" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

ポイントとしては、連続して描画するタイプの要素をアニメーションさせるときは、その開始位置の値を変えていくようにすれば、比較的簡単にできます。上の例ではDrawingBrushをTileMode="Tile"を指定して敷き詰め、位置をViewportで決めていますが、このXの値を変えてアニメーションさせています。

ここでもう一つポイントとして、アニメーションにはプリミティブな値を変えるDoubleAnimationやColorAnimationをよく使いますが、Viewportの型はRectなので、この中のXはDoubleAnimationでは対象に指定できません。こういう場合は、複合的な値であるRectを変えるRectAnimationが用意されているので、これを使えばいいわけです。

同じようにThickness(Marginの型)を変えるThicknessAnimation、Pointを変えるPointAnimation、Sizeを変えるSizeAnimationもあるので、幾何学的に変化させるアニメーションは大体カバーできます。これら以外にも、AnimationTimelineの派生クラスを見ると、予め用意されているAnimationをチェックできます。