I want to be able to smoothly draw a circle (ellipse) so that you can see it being drawn on screen.
Is there anyway possible using DoubleAnimation to do this? If not what is an alternative method?
An example of what I have:
One outer ellipse (black)
Inner ellipse (white) - this is the one I want to animate
Code:
<Ellipse Width="200" Height="200" Stroke="Black" StrokeThickness="20"/>
<Ellipse Width="190" Height="190" Stroke="White" StrokeThickness="10" Canvas.Left="5" Canvas.Top="5" x:Name="animatedEllipse">
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Ellipse.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
I've look at a few examples for example:
http://elegantcode.com/2009/08/21/a-simple-wpf-loading-animation/
http://www.charlespetzold.com/blog/2012/03/The-Animated-Pie-Slice-in-Windows-8.html
Ellipse Drawing WPF Animation
The first two are a bit confusimg for me being new to WPF animation. The latter has 7 votes as "Correct answer" but it is not working for me as I get error "the collection element StrokeDashArray[0] is not a dependency property" (and also I don't want dashes, Although I tried this even on an ellipse with dashes as per the above article and it still failed on this error.
Update:
A method I got sort of working using code is this:
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
public partial class Page1 : Page
{
private void Page_Loaded_1(object sender, RoutedEventArgs e)
{
path = new Path();
group = new GeometryGroup();
path.Data = group;
path.Stroke = new SolidColorBrush(Colors.White);
path.StrokeThickness = 3;
canvas.Children.Add(path);
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
int radius = 90;
for (double i = 0.0; i < 360.0; i += 1)
{
double angle = i * System.Math.PI / 180;
double x = (int)(100 + radius * System.Math.Cos(angle));
double y = (int)(100 + radius * System.Math.Sin(angle));
canvas.Dispatcher.Invoke(new Action(delegate
{
group.Children.Add(new EllipseGeometry(new Point(x, y), 5, 5));
}));
canvas.Refresh();
System.Threading.Thread.Sleep(1);
}
}
You might need three elements:
Outer circle (fill color will be light colors).
inner circle with transparent fill color.
Arc segment will be having thickness difference of radius between them.
Arc will be positioned at 45 angle , can be animated over the both circles.
This is just an idea, I might need to test it on my own.
You might get further by using an ArcSegment instead of an ellipse:
<PathFigure StartPoint="100,100">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="100,100" IsLargeArc="True"
SweepDirection="CounterClockwise" Point="200,200" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
It needs to be part of the PathFigure - where the start point is specified.
You can then animate Point which is the end point of the arc to go from the start point through 360 degrees to the end point.
Related
I've got a simple demo application that uses an image as the background of an InkCanvas and I scale the strokes when the display of the image is resized so that they remain in the same place relative to the image. Since you can draw -> resize -> draw -> resize -> draw this means I have to scale each stroke a different amount each time by assigning the PointTransform on each stroke.
float thisScale = (float)(scale / _prevScale);
foreach (InkStroke stroke in myCanvas.InkPresenter.StrokeContainer.GetStrokes())
{
float thisPointScale = thisScale * stroke.PointTransform.M11;
stroke.PointTransform = Matrix3x2.CreateScale(new Vector2(thisPointScale));
}
This resizes the length of the strokes perfectly well. However, it does nothing to the thickness of the strokes. This is even more evident when you use a thick or non-uniform pen (eg the highlighter pen).
These link to two screen clips which show the results.
Full-screen - https://1drv.ms/i/s!ArHMZAt1svlBiZZDfrxFqyGU1bJ6MQ
Smaller window - https://1drv.ms/i/s!ArHMZAt1svlBiZZCqHHYaISPfWMMpQ
Any ideas on how I can resize the thickness of the strokes?
Apply a ScaleTransform to the InkCanvas control. That'll take care of scaling the ink stroke,the stroke locations and the background image. Essentially the transform applies to everything contained in the InkCanvas. No need to use the Matrix with the StrokeCollection.
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Red Highlighter "
x:Name="InkRedAttributesButton"
Click="InkRedAttributesButton_Click" />
<Button Content="Blue Highlighter "
x:Name="InkBlueAttributesButton"
Click="InkBlueAttributesButton_Click" />
<Button Content="Scale Down"
x:Name="ScaleDownButton"
Click="ScaleDownButton_Click" />
<Button Content="Scale Up"
x:Name="ScaleUpButton"
Click="ScaleUpButton_Click" />
</StackPanel>
<InkCanvas x:Name="myCanvas"
Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top">
<InkCanvas.Background>
<ImageBrush ImageSource="/SO_Questions;component/Images/Star02.jpg"
Stretch="Fill" />
</InkCanvas.Background>
<InkCanvas.RenderTransform>
<ScaleTransform x:Name="InkCanvasScaleTransform" />
</InkCanvas.RenderTransform>
</InkCanvas>
</Grid>
Code
private void ScaleUpButton_Click(object sender, RoutedEventArgs e) {
InkCanvasScaleTransform.ScaleX += .2;
InkCanvasScaleTransform.ScaleY += .2;
}
private void ScaleDownButton_Click(object sender, RoutedEventArgs e) {
InkCanvasScaleTransform.ScaleX -= .2;
InkCanvasScaleTransform.ScaleY -= .2;
}
private void InkRedAttributesButton_Click(object sender, RoutedEventArgs e) {
DrawingAttributes inkAttributes = new DrawingAttributes();
inkAttributes.Height = 12;
inkAttributes.Width = 12;
inkAttributes.Color = Colors.Red;
inkAttributes.IsHighlighter = true;
myCanvas.DefaultDrawingAttributes = inkAttributes;
}
private void InkBlueAttributesButton_Click(object sender, RoutedEventArgs e) {
DrawingAttributes inkAttributes = new DrawingAttributes();
inkAttributes.Height = 12;
inkAttributes.Width = 12;
inkAttributes.Color = Colors.Blue;
inkAttributes.IsHighlighter = true;
myCanvas.DefaultDrawingAttributes = inkAttributes;
}
Screenshots
Scaled 100%
Scaled 60%
Scaling the InkCanvas doesn't always fix the problem especially if you are wanting to save the scaled ink into a gif image file.
Apparently an InkStroke's PointTransform only transforms the location of the stroke's points, but not the size of the PenTip used to draw the stroke. (Not documented anywhere that I can find, but discovered by trial and error. The name 'PointTransform' is a bit of a clue)
So as well as applying your scaling factor to the PointTransform, you also have to scale the PenTip as follows (modification to your original code):
float thisPointScale = thisScale * stroke.PointTransform.M11;
stroke.PointTransform = Matrix3x2.CreateScale(new Vector2(thisPointScale));
stroke.DrawingAttributes.PenTipTransform = Matrix3x2.CreateScale(new Vector2(thisPointScale));
Hope this helps someone...
To resize the thickness of the strokes you have to change the Size property of the DrawingAttributes. PenTipTransform doesn't work for pencil - it throws an exception.
The point is that you cannot set the DrawingAttributes property of the stroke directly: https://learn.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkdrawingattributes
Here is example how to get this:
static IEnumerable<InkStroke> GetScaledStrokes(IEnumerable<InkStroke> source, float scale)
{
var scaleMatrix = Matrix3x2.CreateScale(scale);
var resultStrokes = source.Select(x => x.Clone()).ToArray();
foreach (var inkStroke in resultStrokes)
{
inkStroke.PointTransform = scaleMatrix;
var da = inkStroke.DrawingAttributes;
var daSize = da.Size;
daSize.Width = daSize.Width * scale;
daSize.Height = daSize.Height * scale;
da.Size = daSize;
inkStroke.DrawingAttributes = da;
}
return resultStrokes;
}
Complete example: https://github.com/ycherkes/ScaledInks
I am trying to reproduce an animation for a button, like the animation of the native camera app buttons of Windows 10 Mobile, but I'm lost.
I'm using this example as a basis:
CameraStarterKit
The rotation of the buttons is already ok. o que eu gostaria de implementar era a animação.
Here is the code that the rotate buttons:
private void UpdateButtonOrientation()
{
int device = ConvertDeviceOrientationToDegress(_deviceOrientation);
int display = ConvertDisplayOrientationToDegrees(_displayOrientation);
if (_displayInformation.NativeOrientation == DisplayOrientations.Portrait)
{
device -= 90;
}
var angle = (360 + display + device) % 360;
var transform = new RotateTransform { Angle = angle };
PhotoButton.RenderTransform = transform;
}
How do I include an animation in this code.
Already grateful for any help!
Add RotateTransform property to your Button
<Button Grid.Row="1"
x:Name="PhotoButton"
Content="PhotoButton"
Click="PhotoButton_Click"
HorizontalAlignment="Center">
<Button.RenderTransform>
<RotateTransform x:Name="PhotoButtonRotateTransform"/>
</Button.RenderTransform>
</Button>
After that, you can create and manage Storyboard in code like this:
private void PhotoButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
AnimateProgressRingSlice(PhotoButtonRotateTransform.Angle + 90);
}
private void AnimateProgressRingSlice(double to, double miliseconds = 350)
{
var storyboard = new Storyboard();
var doubleAnimation = new DoubleAnimation();
doubleAnimation.Duration = TimeSpan.FromMilliseconds(miliseconds);
doubleAnimation.EnableDependentAnimation = true;
doubleAnimation.To = to;
Storyboard.SetTargetProperty(doubleAnimation, "Angle");
Storyboard.SetTarget(doubleAnimation, PhotoButtonRotateTransform);
storyboard.Children.Add(doubleAnimation);
storyboard.Begin();
}
Remark
But be careful if you don't know about RenderTransformOrigin property. Read More Info
I guess you need to use this property with 0.5, 0.5 value, so, modify your XAML and add to Button
RenderTransformOrigin="0.5, 0.5
Look the result:
You can define the animation as a Storyboard in the resources of your page:
<Page.Resources>
<Storyboard x:Name="rotate90">
... some animation that changes the RenderTransform of the button
<DoubleAnimation Storyboard.TargetName="PhotoButtonRotateTransform"
Storyboard.TargetProperty="Angle"
Value="90" x:Name="RotationAnimation" />
</Storyboard>
</Page.Resources>
And then start the animation from your code-behind:
private void UpdateButtonOrientation()
{
//here you can put some logic that
//sets the target value for the RotationAnimation's Value property
rotateButtonStoryboard.Begin();
}
For more information about animations and Storyboards check out the MSDN documentation
I was reading many similar q&a but I didn't get answer I'm searching for. So, I'm making "homework" in Microsoft Bled, I really like storyboards and I know how to trigger them with button click, but does anyone know how to start an animation in c# for example in if sentence.
Thanks for answers and time spend in advance!
You´re looking for this thread.
Another way would be:
Storyboard sb = this.FindResource("Storyboard1") as Storyboard;
if (sb != null){ BeginStoryboard(sb); }
public static class AnimationHelper
{
private static void AnimateOpacity(DependencyObject target, double from, double to)
{
var opacityAnimation = new DoubleAnimation
{
From = from,
To = to,
Duration = TimeSpan.FromMilliseconds(500)
};
Storyboard.SetTarget(opacityAnimation, target);
Storyboard.SetTargetProperty(opacityAnimation, "Opacity");
var storyboard = new Storyboard();
storyboard.Children.Add(opacityAnimation);
storyboard.Begin();
}
/// <summary>
/// Fades in the given dependency object.
/// </summary>
/// <param name="target">The target dependency object to fade in.</param>
public static void FadeIn(DependencyObject target)
{
AnimateOpacity(target, 0, 1);
}
}
You can access storyboards from code-behind by giving it a name and referencing that name to use the Begin method.
<Canvas MouseLeftButtonDown="Handle_MouseDown"
Background="Gray" Width="600" Height="500">
<Canvas.Resources>
<Storyboard x:Name="myStoryboard">
<PointAnimation
x:Name="myPointAnimation"
Storyboard.TargetProperty="Center"
Storyboard.TargetName="MyAnimatedEllipseGeometry"
Duration="0:0:2"/>
</Storyboard>
</Canvas.Resources>
<Path Fill="Blue">
<Path.Data>
<EllipseGeometry x:Name="MyAnimatedEllipseGeometry"
Center="200,100" RadiusX="15" RadiusY="15" />
</Path.Data>
</Path>
</Canvas>
Code-behind:
private void Handle_MouseDown(object sender, MouseButtonEventArgs e)
{
// Retrieve current mouse coordinates.
double newX = e.GetPosition(null).X;
double newY = e.GetPosition(null).Y;
Point myPoint = new Point();
myPoint.X = newX;
myPoint.Y = newY;
myPointAnimation.To = myPoint;
myStoryboard.Begin();
}
So my question is pretty simple:
I have a StackPanel with two objects: a button and an rectangle (which is filled with an image by clicking the button). Now, an additional thing that has to happen is when the button is clicked, the entire stackpanel has to be flipped upside down AND it has to stay in the same place.
I have tried with the RenderTransformOrigin-property set on "0.5,0.5"
but I haven't got any luck with this .. either the stackpanel moved to another location or it disappeared (out of bounds)
<Window x:Class="WpfApplication1.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">
<Grid>
<StackPanel Name="pnlFlip" RenderTransformOrigin="0.5,0.5">
<Button Content="Test" Margin="200,78,197,-78" Name="btnTest" Click="btnTest_Click" Height="30"/>
<Rectangle Margin="175,146,162,-239" Name="rectTest" Fill="Red" Height="127"/>
</StackPanel>
</Grid>
</Window>
The code for my buttons is like this:
private int scale = 1;
private int angle = 180;
private void btnTest_Click(object sender, RoutedEventArgs e)
{
ImageBrush img = new ImageBrush();
img.ImageSource = new BitmapImage(new Uri("pack://application:,,,/WpfApplication1;component/Resources/Images/logo.jpg"));
rectTest.Fill = img;
//Trying a ScaleTranfsformObject
ScaleTransform st = new ScaleTransform();
if(scale == 1)
{
scale = -1;
st.ScaleY = scale;
}
else
{
scale = 1;
st.ScaleY = scale;
}
//Trying a RotateTransform Object
RotateTransform rt = new RotateTransform();
if(angle == 180)
{
rt.Angle = angle;
angle += 180;
}
else
{
rt.Angle = angle;
angle -= 180;
}
pnlFlip.RenderTransform = rt;
}
So what am I doing wrong/how do I fix this?
Using storyboard is better approach than setting this directly in code in click handler. You will get smooth changes in UI.
Following will achieve your objective with XAML only, without any code behind. Setting RenderTransformOrigin in the StackPanel is what keeps it in the center after running the rotate transform.
To activate the animation on click, we just add a Button.Trigger handler which has storyboard on DoubleAnimation to change the angle to 180 degree in the StackPanel. Rest of the XAML is what you had before.
<StackPanel x:Name="pnlFlip" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<RotateTransform />
</StackPanel.RenderTransform>
<Button Content="Test" Margin="200,78,197,-78" Name="btnTest" Height="30">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="180" Storyboard.TargetName="pnlFlip" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
<Rectangle Margin="175,146,162,-239" Name="rectTest" Fill="Red" Height="127"/>
</StackPanel>
This works fine on my computer (minus the code for the image in your Resources.)
Here's how it looks when the application launches:
And here's how it looks after I click the button:
Which part of this falls short of your intentions?
Note that you can use a ternary operator to remove your conditional branches and cut down on code, like so:
angle = angle == 180 ? 0 : 180;
rt.Angle = angle;
I'm randomly generating two rectangles, the first rectangle stays where it is but I want to move/animate the second rectangle so that it's bottom right corner is equalto the position occupied by the top left corner of the first, atm what I've basically got is;
if(count != 300)
{
rect2.X = count + (rect1.X - 100);
rect2.Y = count + (rect1.Y - 100);
count +=2
}
This is inside a timer event, but is obviously not quite what I want, I've tried multiple variations of the above but none seem to do the job.
I'm sure there'll be an annoyingly simple answer to this.
Thanks in Advance.
Assuming top left of the screen is {0,0} and bottom right is {screenWidth, screenHeight}
I'm also assuming floating point coordinates here (i.e. Rect.X is a double or float). If they aren't, you'll need to do some work in step 2 below to make sure you move an even amount of pixels in each step.
Here's the basic algorithm, in pseudo code:
Calculate final location for your animating rectangles top-left corner: finalPos = {r1.x + r1.width, r1.y+r1.width}
Decide how many steps you want, and calculate how far the rectangle should move each step.
Let's take 100 steps for example. dx = r2.x - finalPos.x / 100, dy = r2.y - finalPos.y / 100
In each timer event, you just add dx and dy to your animating rectangle's position:
Like so:
if (currentStep < 300)
{
r2.x += dx;
r2.y += dy;
currentStep++;
}
else
{
// We're done
timer.Stop();
}
Of course, if you are using a graphical framework to draw stuff, you may already have access to an animation framework that can do the above for you.
If you are able to use WPF it is pretty straightforward:
In the window xaml:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="600">
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="AnimatedRect"
Storyboard.TargetProperty="(Canvas.Left)"
To="{Binding ElementName=FixedRect, Path=(Canvas.Left)}" Duration="0:0:1"/>
<DoubleAnimation
Storyboard.TargetName="AnimatedRect"
Storyboard.TargetProperty="(Canvas.Top)"
To="{Binding ElementName=FixedRect, Path=(Canvas.Top)}" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Canvas>
<Rectangle Name="FixedRect"
StrokeThickness="2"
Stroke="Black"
Width="{Binding R1.Width}"
Height="{Binding R1.Height}"
Canvas.Left="{Binding R1.X}"
Canvas.Top="{Binding R1.Y}"/>
<Rectangle Name="AnimatedRect"
StrokeThickness="2"
Stroke="Black"
Width="{Binding R2.Width}"
Height="{Binding R2.Height}"
Canvas.Left="{Binding R2.X}"
Canvas.Top="{Binding R2.Y}"/>
</Canvas>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Random rnd = new Random();
Rect r1 = new Rect();
r1.X = rnd.Next(500);
r1.Y = rnd.Next(500);
r1.Width = rnd.Next(50,100);
r1.Height = rnd.Next(50, 100);
R1 = r1;
Rect r2 = new Rect();
r2.X = rnd.Next(500);
r2.Y = rnd.Next(500);
r2.Width = rnd.Next(50, 100);
r2.Height = rnd.Next(50, 100);
R2 = r2;
DataContext = this;
}
public Rect R1 { get; set; }
public Rect R2 { get; set; }
}