Reputation: 740
I am trying to create a 3..2..1 countdown in the form of a user control. Something like this. My Idea was to create two rectangles on top of each other, one light and one dark and have a radial circle as the clipper for the dark rectangle. The radial circle would have Angle property animated so it would turn around.
I found an implementation of the radial circle and bound the Clip property of the rectangle on the RenderedGeometry property of my circle. Here is the result :
The red stroke is the shape of my clipper. This seems to be an odd behavior of the clipping but I sort of understand it but I would like to know if there was a way of going around the fact that my clipped object seems to use the RenderedGeometry in a weird way.
Edit 1 : The effect I am looking for http://www.youtube.com/watch?v=9FPHTo5V2BQ
Upvotes: 2
Views: 4102
Reputation: 13017
You can clip your rectangle by using an ArcSegment
in the clipping PathGeometry
, and animate that ArcSegment
's endpoint (Point
).
The endpoint can be animated with a PointAnimationUsingPath
animation, using an identical ArcSegment
as its path. Below is a suggestion based on Charlez Petzold's excellent answer here: Drawing pie slices
<UserControl ... >
<UserControl.Resources>
<Point x:Key="SweepCenter" X="100" Y="100" />
<Size x:Key="SweepRadius" Width="130" Height="130" />
<!-- Start sweeping at twelve o'clock.. -->
<Point x:Key="SweepStart" X="100" Y="-30" />
<!-- ..and keep sweeping clockwise until we're (almost) back at the start point: -->
<Point x:Key="SweepEnd" X="99.99" Y="-30" />
<Storyboard x:Key="Sweeper" RepeatBehavior="Forever" AutoReverse="False" >
<PointAnimationUsingPath Storyboard.TargetName="arc"
Storyboard.TargetProperty="Point"
Duration="0:0:5">
<PointAnimationUsingPath.PathGeometry>
<PathGeometry>
<PathFigure StartPoint="{StaticResource SweepStart}">
<ArcSegment Size="{StaticResource SweepRadius}"
Point="{StaticResource SweepEnd}"
SweepDirection="Clockwise"
IsLargeArc="True" />
</PathFigure>
</PathGeometry>
</PointAnimationUsingPath.PathGeometry>
</PointAnimationUsingPath>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="arc"
Storyboard.TargetProperty="IsLargeArc" >
<DiscreteBooleanKeyFrame KeyTime="0:0:2.5" Value="True" />
<DiscreteBooleanKeyFrame KeyTime="0:0:5" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid Width="200" Height="200" >
<Rectangle Fill="Black" />
<Rectangle Fill="Gray" >
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard Storyboard="{StaticResource Sweeper}" />
</EventTrigger>
</Rectangle.Triggers>
<Rectangle.Clip>
<PathGeometry>
<PathFigure StartPoint="{StaticResource SweepCenter}"
IsClosed="True" >
<LineSegment Point="{StaticResource SweepStart}" />
<ArcSegment x:Name="arc"
Size="{StaticResource SweepRadius}"
Point="{StaticResource SweepStart}"
SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry>
</Rectangle.Clip>
</Rectangle>
</Grid>
</UserControl>
Upvotes: 3
Reputation: 128013
The simple derived Shape control shown below draws the countdown rectangle. You have to set its Fill
(and perhaps Stroke
), Width
, Height
and Angle
properties, and you can animate Angle
from 0 to 360.
public class CountdownRect : Shape
{
static CountdownRect()
{
WidthProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
HeightProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
StrokeLineJoinProperty.OverrideMetadata(typeof(CountdownRect),
new FrameworkPropertyMetadata(PenLineJoin.Round));
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CountdownRect),
new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
private readonly StreamGeometry geometry = new StreamGeometry();
protected override Geometry DefiningGeometry
{
get { return geometry; }
}
private void UpdateGeometry()
{
if (!double.IsNaN(Width) && !double.IsNaN(Height))
{
var angle = ((Angle % 360d) + 360d) % 360d;
var margin = StrokeThickness / 2d;
var p0 = new Point(margin, margin);
var p1 = new Point(Width - margin, margin);
var p2 = new Point(Width - margin, Height - margin);
var p3 = new Point(margin, Height - margin);
using (var context = geometry.Open())
{
if (angle == 0d)
{
context.BeginFigure(p0, true, true);
context.LineTo(p1, true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
}
else
{
var x = p2.X / 2d;
var y = p2.Y / 2d;
var a = Math.Atan2(x, y) / Math.PI * 180d;
var t = Math.Tan(angle * Math.PI / 180d);
context.BeginFigure(new Point(x, y), true, true);
if (angle < a)
{
context.LineTo(new Point(x + y * t, p0.Y), true, false);
context.LineTo(p1, true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 180d - a)
{
context.LineTo(new Point(p2.X, y - x / t), true, false);
context.LineTo(p2, true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 180d + a)
{
context.LineTo(new Point(x - y * t, p2.Y), true, false);
context.LineTo(p3, true, false);
context.LineTo(p0, true, false);
}
else if (angle < 360d - a)
{
context.LineTo(new Point(p0.X, y + x / t), true, false);
context.LineTo(p0, true, false);
}
else
{
context.LineTo(new Point(x + y * t, p0.Y), true, false);
}
context.LineTo(new Point(x, p0.Y), true, false);
}
}
}
}
}
Upvotes: 3