WPF polygon storyboard animation - wpf

Trying to make exact animation from FloatingMusicActionButton
My code till now:
<Grid Width="128" Height="128" Panel.ZIndex="1">
<Ellipse Fill="Aqua"></Ellipse>
<Polygon Fill="LightBlue" Stroke="Black" Name="TriOne" >
<Polygon.Points>
<Point X="44" Y="32"></Point>
<Point X="44" Y="64"></Point>
<Point X="100" Y="64"></Point>
<Point X="100" Y="64"></Point>
</Polygon.Points>
<Polygon.Triggers>
<EventTrigger RoutedEvent="Polygon.MouseUp">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="{Binding ElementName=TriOne, Path=Points[0]}"
Storyboard.TargetProperty="X"
From="44" To="32"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Polygon.Triggers>
</Polygon>
<Polygon Fill="LightBlue" Stroke="Black" Name="TriTwo" >
<Polygon.Points>
<Point X="44" Y="96"></Point>
<Point X="44" Y="64"></Point>
<Point X="100" Y="64"></Point>
</Polygon.Points>
</Polygon>
</Grid>
Application goes into break mode after clicking the polygon throwing:
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll
'44,32' name cannot be found in the name scope of 'System.Windows.Shapes.Polygon'.
I'm new to WPF, if you know a better method of animating polygons please share a link.

Your animation cannot work because the X property of Point is not a dependency property.
Use a Path instead of a Polygon and animate the PathFigures:
<Grid Width="128" Height="128" Panel.ZIndex="1">
<Ellipse Fill="Aqua"></Ellipse>
<Path Fill="LightBlue" Stroke="Black" Name="TriOne">
<Path.Data>
<PathGeometry>
<PathFigure x:Name="fig" StartPoint="44, 32" IsClosed="True">
<LineSegment Point="44, 64"/>
<LineSegment x:Name="middle" Point="100, 64"/>
<LineSegment Point="100, 64"/>
</PathFigure>
</PathGeometry>
</Path.Data>
<Path.Triggers>
<EventTrigger RoutedEvent="Polygon.MouseUp">
<BeginStoryboard>
<Storyboard>
<PointAnimation
Storyboard.TargetName="fig"
Storyboard.TargetProperty="StartPoint"
From="44,32" To="32,32"
/>
<PointAnimation
Storyboard.TargetName="middle"
Storyboard.TargetProperty="Point"
From="100, 64" To="90, 54"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Path.Triggers>
</Path>
<Polygon Fill="LightBlue" Stroke="Black" Name="TriTwo" >
<Polygon.Points>
<Point X="44" Y="96"></Point>
<Point X="44" Y="64"></Point>
<Point X="100" Y="64"></Point>
</Polygon.Points>
</Polygon>
</Grid>

Related

PointCollection resource from individual Point resources in XAML?

If I had to create a collection of points as XAML resource, I'd do this:
<Window.Resources>
<PointCollection x:Key="points">
<Point>0,30</Point>
<Point>20,50</Point>
<Point>40,10</Point>
</PointCollection>
</Window.Resources>
In my case the points are already resources:
<Window.Resources>
<Point x:Key="a" X="100" Y="100"/>
<Point x:Key="b" X="200" Y="100"/>
<Point x:Key="b1a" X="100" Y="0"/>
<Point x:Key="b1b" X="200" Y="0"/>
</Window.Resources>
and this way (which is probably already over-killing) doesn't work, as X/Y are not dependency properties:
<Window.Resources>
<PointCollection x:Key="b1points">
<Point X="{Binding Source={StaticResource b1a}, Path=X}"
Y="{Binding Source={StaticResource b1a}, Path=Y}"/>
<Point X="{Binding Source={StaticResource b1b}, Path=X}"
Y="{Binding Source={StaticResource b1b}, Path=Y}"/>
<Point X="{Binding Source={StaticResource b}, Path=X}"
Y="{Binding Source={StaticResource b}, Path=Y}"/>
</Window.Resources>
The collection is used in a Bezier segment later:
<PolyBezierSegment Points="{StaticResource b1points}"/>
but the points must be declared individually, so that they can be used like:
<Ellipse Canvas.Left="{Binding Source={StaticResource a}, Path=X}"
Canvas.Top="{Binding Source={StaticResource a}, Path=Y}"
Width="3" Height="3" Fill="Red"/>
Is someone able to suggest a mean in XAML? and even more difficult, without a converter?
This should work:
<Window.Resources>
<Point x:Key="a" X="100" Y="100"/>
<Point x:Key="b" X="200" Y="100"/>
<Point x:Key="b1a" X="100" Y="0"/>
<Point x:Key="b1b" X="200" Y="0"/>
<PointCollection x:Key="b1points">
<StaticResource ResourceKey="b1a"/>
<StaticResource ResourceKey="b1b"/>
<StaticResource ResourceKey="a"/>
<StaticResource ResourceKey="b"/>
</PointCollection>
</Window.Resources>
...
<PolyBezierSegment Points="{StaticResource b1points}"/>
...
<Path Fill="Red">
<Path.Data>
<EllipseGeometry Center="{StaticResource a}" RadiusX="1.5" RadiusY="1.5"/>
</Path.Data>
</Path>

Animation slows down when left margin becomes negative

I am trying to have a polygon move from completely off the left of the screen, across the screen, and then completely off the right of the screen, then back again.
I've gotten this working. BUT, for some reason, as soon as the left margin becomes negative, the animation suddenly slows down. As soon as the left margin becomes positive, it speeds up again.
Why does this happen? How can I stop it?
Here's the complete code that demonstrates this:
<Window x:Class="Geometry.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<PathGeometry x:Key="MyGeometry">
<PathGeometry.Figures>
<PathFigure>
<PathFigure.Segments>
<LineSegment Point="0.30,0" />
<LineSegment Point="0.70,1" />
<LineSegment Point="0.40,1" />
<LineSegment Point="0,0" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
<Storyboard x:Key="MovingAnimation">
<ThicknessAnimationUsingKeyFrames RepeatBehavior="1:0:0" FillBehavior="HoldEnd" Storyboard.TargetName="_path" Storyboard.TargetProperty="Margin" >
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="-2.0,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:10" Value="1.0,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:20" Value="-2.0,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard Storyboard="{StaticResource MovingAnimation}" ></BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label>Margin:</Label>
<TextBlock Text="{Binding ElementName=_path, Path=Margin.Left, StringFormat={}{0:0.#}}" />
</StackPanel>
<Canvas Name="_canvas" Grid.Row="1">
<Border Margin="0" Width="1" Height="1" VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.RenderTransform>
<ScaleTransform
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</Border.RenderTransform>
<Path
Name="_path"
Fill="#CCCCFF"
Data="{StaticResource MyGeometry}"
Width="1.0"
Height="1.0"
>
</Path>
</Border>
</Canvas>
</Grid>
</Window>
I don't have an explanation why the negative margin animates slowly, but I found a workaround.
Instead of animating the Margin of the Path I animated the X value of a TranslateTransform on the Border object that contains the path.
I had to put the TranslateTransform in front of the ScaleTransform so that the translation was applied before the scale. That allows you to use almost the same values in your animation that you used for the Margin.
<Storyboard x:Key="MovingAnimation">
<ThicknessAnimationUsingKeyFrames RepeatBehavior="1:0:0" Storyboard.TargetName="_blank" Storyboard.TargetProperty="Margin" >
<LinearThicknessKeyFrame KeyTime="0:0:0" Value="-1.5,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:10" Value="1,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:20" Value="-1.5,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
The ugly part is that I couldn't find a quick way to apply the values in the key frames directly to the X property of the TranslateTransform, so I cheated and used element binding and a placeholder Canvas object.
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5">
<TextBlock Margin="0,0,5,0" Text="Margin.Left:"/>
<TextBlock Text="{Binding ElementName=_blank, Path=Margin.Left, StringFormat={}{0:0.#}}" />
</StackPanel>
<Canvas Name="_blank" /> <!--Placeholder object-->
<Canvas Name="_canvas" Grid.Row="1">
<Border Margin="0" Width="1" Height="1"
Name="_border"
VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform X="{Binding Margin.Left, ElementName=_blank}"/>
<ScaleTransform
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</TransformGroup>
</Border.RenderTransform>
Even thought the animation is still being applied to a Margin and then to a TranslateTransform through element binding, the delay for a negative margin is gone.
I suspect that the negative margin delay has something to do with the Path being in the Border that was being scaled, but that is conjecture on my part.
If you can find a way to bind the KeyFrame values directly to the X property of the TranslateTransform, that would make this workaround much less ugly.
EDIT:
Figured out the proper binding to use:
<Storyboard x:Key="MovingAnimation2">
<DoubleAnimationUsingKeyFrames RepeatBehavior="1:0:0" Storyboard.TargetName="tt" Storyboard.TargetProperty="X" >
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="-1.5" />
<LinearDoubleKeyFrame KeyTime="0:0:5" Value="1" />
<LinearDoubleKeyFrame KeyTime="0:0:10" Value="-1.5" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
This gets rid of the extra canvas placeholder object.
<Canvas Name="_canvas" Grid.Row="1">
<Border Margin="0" Width="1" Height="1"
Name="_border"
VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="tt"/>
<ScaleTransform
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</TransformGroup>
</Border.RenderTransform>
Animating Margin property will trigger additional measure/arrange pass which in turn cause a bit more performance impact (though in this example it may not be noticeable). Animation of "render-only" properties on the other hand will not trigger layout re-arrangement and, thus, is more performance friendly.
Please, take a look at a bit easier way to do what, I suppose, you are want to get:
<Window x:Class="Geometry.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="518" Width="530">
<Window.Resources>
<PathGeometry x:Key="MyGeometry">
<PathGeometry.Figures>
<PathFigure>
<PathFigure.Segments>
<LineSegment Point="0.30,0" />
<LineSegment Point="0.70,1" />
<LineSegment Point="0.40,1" />
<LineSegment Point="0,0" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
<Storyboard x:Key="MovingAnimation">
<DoubleAnimationUsingKeyFrames RepeatBehavior="1:0:0" FillBehavior="HoldEnd" Storyboard.TargetName="_scaleTransform" Storyboard.TargetProperty="CenterX" >
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="1.2" />
<LinearDoubleKeyFrame KeyTime="0:0:10" Value="-0.5" />
<LinearDoubleKeyFrame KeyTime="0:0:20" Value="1.2" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard Storyboard="{StaticResource MovingAnimation}" ></BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label>Margin:</Label>
<TextBlock Text="{Binding ElementName=_scaleTransform, Path=CenterX, StringFormat={}{0:0.#}}" VerticalAlignment="Center" />
</StackPanel>
<!--
<Border Grid.Row="1" Margin="150" BorderBrush="Red" BorderThickness="1">
-->
<Grid Name="_canvas" Grid.Row="1">
<Path Name="_path" Fill="#CCCCFF" Data="{StaticResource MyGeometry}"
Width="1.0"
Height="1.0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path.RenderTransform>
<ScaleTransform x:Name="_scaleTransform"
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="1.2"
CenterY="0.5">
</ScaleTransform>
</Path.RenderTransform>
</Path>
</Grid>
<!--
</Border>
-->
</Grid>
</Window>
From the two answers so far, it's clear that if you want to have a shape fly around the screen, don't animate the margins.
Stewbob solved the problem by animating the X value of a translate transform.
sevenate solved the problem by animating the Center X value of a scale transform.
Another solution is, instead of wrapping the polygon in a border and animating the margin, wrap it up in a canvas and animate the left value.
Wrapping it in a canvas:
<Canvas Name="_canvasFrame" Grid.Row="1">
<Canvas Margin="0" Width="1" Height="1">
<Canvas.RenderTransform>
<ScaleTransform
ScaleX="{Binding ElementName=_canvasFrame, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvasFrame, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</Canvas.RenderTransform>
<Path
Name="_path"
Fill="#CCCCFF"
Data="{StaticResource MyGeometry}"
Width="1.0" Height="1.0"
>
</Path>
</Canvas>
</Canvas>
Then animating the left value:
<Storyboard x:Key="MovingAnimation">
<DoubleAnimationUsingKeyFrames
RepeatBehavior="1:0:0"
FillBehavior="HoldEnd"
Storyboard.TargetName="_path"
Storyboard.TargetProperty="(Canvas.Left)" >
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="-1.0" />
<LinearDoubleKeyFrame KeyTime="0:0:10" Value="1.0" />
<LinearDoubleKeyFrame KeyTime="0:0:20" Value="-1.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>

Mystery of Points in XAML

I am trying to create a polygon using points in xaml and as per my understanding the output with the given points should be the triangle with black fill, but it return the triangle with pink fill. I am not getting how this is happening. Kindly let me know.
Tha xaml for this is
<Polygon Width="237"
Height="214"
Fill="White"
Stroke="Black"
StrokeThickness="2">
<Polygon.Points>
<Point X="50" Y="50" />
<Point X="150" Y="150" />
<Point X="50" Y="150" />
</Polygon.Points>
</Polygon>
The Point X=0 and Y=0 is in the upper left corner, not in the lower left corner. So the drawing is correct.
To get what you want is to change your xaml as follows:
<Polygon Width="237"
Height="214"
Fill="Black"
Stroke="White"
StrokeThickness="2">
<Polygon.Points>
<Point X="50" Y="150" />
<Point X="150" Y="150" />
<Point X="150" Y="50" />
</Polygon.Points>
<Polygon>
The point system is the same one used in a Canvas, where 0,0 is the top left corner
For example, the point 50,50 is like saying Canvas.Left="50" and Canvas.Top="50"
To get the shape you want, you need to adjust the points so they read from the top-left instead of the bottom-left.
<Polygon Width="237"
Height="214"
Fill="White"
Stroke="Black"
StrokeThickness="2">
<Polygon.Points>
<Point X="50" Y="50" />
<Point X="150" Y="50" />
<Point X="150" Y="150" />
</Polygon.Points>
</Polygon>
<Point X="50" Y="150" /> is wrong location - that's all.
should be: <Point X="150" Y="50" />
Simple X Y interchange mistake, there is nothing wrong with your understanding.

Constraining dimension of a PlaneProjection

How can I constrain a plane projection to a particular dimension? For example, I have the following:
<Canvas Width="720" Height="540" x:Name="Root" Background="Red" >
<Line Width="200" Height="5" X1="0" X2="200"
Y1="0" Y2="0" Stroke="LimeGreen" StrokeThickness="10"
Canvas.Left="260" Canvas.Top="70" />
<Rectangle Width="200" Height="400" Stroke="Blue" StrokeThickness="6"
Fill="LightBlue" Opacity="0.5" Canvas.Left="260" Canvas.Top="70">
<Rectangle.Projection>
<PlaneProjection x:Name="box" />
</Rectangle.Projection>
</Rectangle>
<Line Width="200" Height="10" X1="0" X2="200"
Y1="0" Y2="0" Stroke="LimeGreen" StrokeThickness="10"
Canvas.Left="260" Canvas.Top="464" />
</Canvas>
I want to rotate this around its Y axis by 360 degrees, but never want the projection to exceed the bounds of the height of the rectangle - in this case, 400 points.
The storyboard, just for simplicities' sake in testing, is in a trigger.
<UserControl.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="box"
Storyboard.TargetProperty="RotationY"
By="360" Duration="0:0:15"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</UserControl.Triggers>
This is not perfect, as it is linear interpolation and the actual relationship of the height to angle of rotation is a slight sinusoidal curve, but is close to what you want.
It basically scales Y to 0.88 in one quarter of the total time (and is AutoReversed).
You can simplify the element naming as you did with "box" if you prefer (I used Blend to author/test the storyboard and it always creates the long element names):
<Storyboard>
<DoubleAnimation Storyboard.TargetName="box"
Storyboard.TargetProperty="RotationY"
By="360" Duration="0:0:15"
RepeatBehavior="Forever" />
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
Storyboard.TargetName="rectangle"
Duration="0:0:3.75" To="0.88"
RepeatBehavior="Forever"
AutoReverse="True" >
</DoubleAnimation>
</Storyboard>
<Rectangle x:Name="rectangle"
Stroke="Blue"
StrokeThickness="6"
Fill="LightBlue"
Opacity="0.5"
RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<CompositeTransform/>
</Rectangle.RenderTransform>
<Rectangle.Projection>
<PlaneProjection x:Name="box" />
</Rectangle.Projection>
</Rectangle>

WPF Clip Rectangle

I have a UserControl that is 45x45 (hardcoded size - it's part of a grid of items). When a certain property has a value I want to show a "clip" in the upper right-hand corner indicating this. The visibility itself is easy to accomplish, however my Rectangle goes outside the bounds of the control and overlaps on other controls. I tried using the "ClipToBounds" property, but it didn't do anything. When I added that to the overall control the clip worked perfectly but my hover effects on the main rectangle (fills the cell) stopped working.
Any ideas? I'm confident this is simple to do, but still being pretty new to WPF (and horrible at geometry - hence not using the Polygon) I'm a bit lost.
Here is the full markup:
<Grid>
<Rectangle x:Name="MainRectangle" Fill="{Binding Background}" Opacity="0" MouseEnter="MainRectangle_MouseEnter" MouseLeave="MainRectangle_MouseLeave">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="0.8" Duration="0:0:0.33" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Rectangle.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.8" To="0.0" Duration="0:0:0.33" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding VisualCount}" />
<Rectangle Fill="Black" HorizontalAlignment="Right" Height="20" Margin="0,-14,-20,0" Stroke="Black" VerticalAlignment="Top" Width="20" Visibility="{Binding HasNotes}">
<Rectangle.RenderTransform>
<RotateTransform CenterX="0" CenterY="0" Angle="45" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
Here be a simple path:
<Path Fill="Black" HorizontalAlignment="Right" Visibility="{Binding HasNotes}">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="14,0"/>
<LineSegment Point="14,14"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>

Resources