The following code is working fine.
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:.8" Storyboard.TargetProperty="Left" From="1920" To="0" AccelerationRatio=".1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
But in this From and To values are static. I need to pass the values dynamically based system resolution. So i need it to be created in code behind. Is it possible to do ?
How to convert it to codebehind?
When working in code, you don't need Storyboard really, just animations for basic things, like you show in your question.
I made a little sample to show how easy it works.
This is the complete code behind of the mainwindow:
namespace WpfCSharpSandbox
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
WidenObject(150, TimeSpan.FromSeconds(1));
}
private void WidenObject(int newWidth, TimeSpan duration)
{
DoubleAnimation animation = new DoubleAnimation(newWidth, duration);
rctMovingObject.BeginAnimation(Rectangle.WidthProperty, animation);
}
}
}
This is how the XAML looks like:
<Window x:Class="WpfCSharpSandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sandbox" Height="350" Width="525">
<Grid Background="#333333">
<Rectangle x:Name="rctMovingObject" Fill="LimeGreen" Width="50" Height="50"/>
</Grid>
</Window>
Put this in a WPF app and see how it works, experiment with it and try other animations/properties.
Adding djerry's comment sample code would look like this:
var anim = new DoubleAnimation {
From = 1920,
To = 1,
};
wnd.BeginAnimation(Window.LeftProperty, anim);
and you would have to have this code in window loaded event handler. Hope this helps.
The question's example code was about animating the Window.Left property and I was looking for exact that case, but the given answer does work for an one-time use-case only.
Specifically: If the animation has been performed and the Window is then moved manually via drag&drop, the same animation procedure will not work again as desired. The animation will always use the end-coordinates of the recent animation run.
So if you moved the window, it will jump back before starting the new animation:
https://imgur.com/a/hxRCqm7
To solve that issue, it is required to remove any AnimationClock from the animated property after the animation is completed.
That is done by using ApplyAnimationClock or BeginAnimation with null as the second parameter:
public partial class MainWindow : Window
{
// [...]
private void ButtonMove_Click(object sender, RoutedEventArgs e)
{
AnimateWindowLeft(500, TimeSpan.FromSeconds(1));
}
private void AnimateWindowLeft(double newLeft, TimeSpan duration)
{
DoubleAnimation animation = new DoubleAnimation(newLeft, duration);
myWindow.Completed += AnimateLeft_Completed;
myWindow.BeginAnimation(Window.LeftProperty, animation);
}
private void AnimateLeft_Completed(object sender, EventArgs e)
{
myWindow.BeginAnimation(Window.LeftProperty, null);
// or
// myWindow.ApplyAnimationClock(Window.LeftProperty, null);
}
}
XAML:
<Window x:Class="WpfAppAnimatedWindowMove.MainWindow"
// [...]
Name="myWindow">
Result:
https://imgur.com/a/OZEsP6t
See also Remarks section of Microsoft Docs - HandoffBehavior Enum
Related
I have this code. This is just an example. I want to do it with code. How to animate the Foreground property of multiple "Run" elements in a TextBlock?
<Page
x:Class="AnimationTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:AnimationTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid>
<TextBlock
x:Name="_textBlockElement"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="72"><Run x:Name="_run1" Text="Hel" /><Run Text="lo" />
<TextBlock.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard x:Name="ColorStoryboard">
<ColorAnimation
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetName="_textBlockElement"
Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
To="Red"
Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Grid>
</Page>
Oke, so this took quite some sorting out, but turned out to be quite doable.
The key is to create two storyboards in code behind with the correct animations and then add those storyboards to the resources of any parent of the Run's.
Let's start of with the XAML code, which is pretty simple:
<Grid>
<TextBlock x:Name="TestBlock"
HorizontalAlignment="Center" VerticalAlignment="Center"
PointerEntered="TestBlock_PointerEntered"
PointerExited="TestBlock_PointerExited">
<Run x:Name="Run1" Text="Test1" Foreground="Blue"/>
<Run x:Name="Run2" Text="Test2" Foreground="Green"/>
<!-- ... -->
</TextBlock>
</Grid>
For simplicity I have already defined the names and foregrounds of the Run's.
Now we need to define the storyboards and animations in code behind.
I've chosen to do this in the constructor (after InitializeComponent()!). In theory you should be able to also paste this code in the Page_Loaded event.
public MainPage()
{
InitializeComponent();
SetupStoryBoards();
}
void SetupStoryBoards()
{
// Define duration and storyboards to red and original color
var duration = new Duration(TimeSpan.FromSeconds(1));
var toRedStory = new Storyboard { Duration = duration };
// completed events can be subscribed to, to register when animation is done
//toRedStory.Completed += Story_Completed;
var toOriginalStory = new Storyboard { Duration = duration };
//toOriginalStory.Completed += ToOriginalStory_Completed;
foreach (Run r in TestBlock.Inlines)
{
// Filter out any inlines that are not a named Run
if (string.IsNullOrEmpty(r.Name))
continue;
// Define the animations
var toRedAnim = new ColorAnimation
{
Duration = duration,
To = Colors.Red,
EnableDependentAnimation = true
};
var toOriginalAnim = new ColorAnimation
{
Duration = duration,
To = (r.Foreground as SolidColorBrush).Color, // Causes animation to go back to original foreground color of Run
EnableDependentAnimation = true
};
// Add animations to the storyboards and associate animations with the Run
toRedStory.Children.Add(toRedAnim);
toOriginalStory.Children.Add(toOriginalAnim);
Storyboard.SetTargetName(toRedAnim, r.Name);
Storyboard.SetTargetName(toOriginalAnim, r.Name);
Storyboard.SetTargetProperty(toRedAnim, "(Run.Foreground).(SolidColorBrush.Color)");
Storyboard.SetTargetProperty(toOriginalAnim, "(Run.Foreground).(SolidColorBrush.Color)");
}
// Add the storyboards to the resources of any parent of the Run's for easy retrieval later and to make the animations find the Run's
// I choose the resources of the textblock that contains the Run's
TestBlock.Resources.Add("toRedStory", toRedStory);
TestBlock.Resources.Add("toOriginalStory", toOriginalStory);
}
Now to execute the animations, we add the PointerEntered and PointerExited eventhandlers, and begin the correct storyboards there:
private void TextBlock_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var story = TestBlock.Resources["toRedStory"] as Storyboard;
story.Begin();
}
private void TextBlock_PointerExited(object sender, PointerRoutedEventArgs e)
{
var story = TestBlock.Resources["toOriginalStory"] as Storyboard;
story.Begin();
}
You should be able to extend this wherever needed, however I have found that EnableDependentAnimation must be set to true since otherwise it won't work.
I'm now just trying to create a simple image viewer with panning and zooming, for studying WPF.
I wrote my XAML code like this:
<Window x:Class="PanningImageTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PanningImageTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border Name="panBorder" ClipToBounds="True">
<Canvas Name="panImage" MouseLeftButtonDown="Image_MouseLeftButtonDown"
MouseMove="Image_MouseMove"
MouseLeftButtonUp="Image_MouseLeftButtonUp">
<!-- Two Image is intended, as in real situation,
they will use different sources -->
<Image Source="Wallpaper.jpg" Stretch="None"/>
<Image Source="Wallpaper.jpg" Stretch="None"/>
</Canvas>
</Border>
</Grid>
</Window>
And wrote my code like this:
using System.Windows;
using System.Windows.Media;
namespace PanningImageTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Point _start;
private Point _origin;
private void Image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.panImage.CaptureMouse();
_start = e.GetPosition(this.panBorder);
_origin.X = this.panImage.RenderTransform.Value.OffsetX;
_origin.Y = this.panImage.RenderTransform.Value.OffsetY;
}
private void Image_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!this.panImage.IsMouseCaptured)
{
return;
}
Point mousePos = e.GetPosition(this.panBorder);
Matrix matrix = this.panImage.RenderTransform.Value;
matrix.OffsetX = _origin.X + (mousePos.X - _start.X);
matrix.OffsetY = _origin.Y + (mousePos.Y - _start.Y);
this.panImage.RenderTransform = new MatrixTransform(matrix);
}
private void Image_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.panImage.ReleaseMouseCapture();
}
}
}
If I call this.panImage.CaptureMouse() BEFORE the _start = ... etc ... lines, All the Images just teleports to the position of the mouse as soon as I clicked somewhere on the image.
Like this:
Call before
But if I call the same function after the lines, like the following, it just works as intended:
_start = e.GetPosition(this.panBorder);
_origin.X = this.panImage.RenderTransform.Value.OffsetX;
_origin.Y = this.panImage.RenderTransform.Value.OffsetY;
this.panImage.CaptureMouse();
Like this:
Call After
If I use only one Image tag in Canvas, it works nicely in both cases.
I tried changing .NET versions, moving events to Border instead of Canvas, but everything just failed to explain these results.
I have no idea why this happens. Can anyone give some explanations?
MouseMove event has started to be fired even the MouseLeftButtonDown method has not yet finished.
So you call CaptureMouse() method as the last line, and use the following check as a guard to prevent the image from being dragged when you have not yet gotten the value of _origin.
if (!this.panImage.IsMouseCaptured)
{
return;
}
You need to set the start position and the coordinates before you start to calculate the offsets. Otherwise you get the offsets wrong. So it makes no sense to capture the mouse before you have done this.
Note that the MouseMove event will be raised as soon as you move the mouse(all the time basically) but until you have captured it, the event handler will just return without moving the image by setting its RenderTransform property.
So you should set the start position and the X and Y coordinates and then move the image based on these. Not the other way around.
I'm making a WPF application using C# and Visual Studio and I need a button to jump to a random location in the window when the mouse hovers over it. I then need the same thing to happen when you hover over it again. I also don't need an animation, just for it to jump there.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void butNo_MouseEnter(object sender, MouseEventArgs e)
{
}
}
I tried this but it didn't work and is also an animation:
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0" To="100" Duration="0:0:2" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
Any help would be much appreciated, thanks.
Edit:
Tried this but 'Location' comes with error code CS1061 in VS.
private void butNo_MouseEnter(object sender, MouseEventArgs e)
{
Random x = new Random();
Point pt = new Point(int.Parse(x.Next(200).ToString()), int.Parse(x.Next(250).ToString()));
butNo.Location = pt;
}
Here's a solution using a Canvas and animations.
<Canvas Name="cnv">
<Button Name="btn"
Content="Click Me!"
IsTabStop="False"
Canvas.Left="0"
Canvas.Top="0"
MouseEnter="Button_MouseEnter"/>
</Canvas>
And the code-behind:
Random rnd = new Random();
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
//Work out where the button is going to move to.
double newLeft = rnd.Next(Convert.ToInt32(cnv.ActualWidth - btn.ActualWidth));
double newTop = rnd.Next(Convert.ToInt32(cnv.ActualHeight - btn.ActualHeight));
//Create the animations for left and top
DoubleAnimation animLeft = new DoubleAnimation(Canvas.GetLeft(btn), newLeft, new Duration(TimeSpan.FromSeconds(1)));
DoubleAnimation animTop = new DoubleAnimation(Canvas.GetTop(btn), newTop, new Duration(TimeSpan.FromSeconds(1)));
//Set an easing function so the button will quickly move away, then slow down
//as it reaches its destination.
animLeft.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
animTop.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
//Start the animation.
btn.BeginAnimation(Canvas.LeftProperty, animLeft, HandoffBehavior.SnapshotAndReplace);
btn.BeginAnimation(Canvas.TopProperty, animTop, HandoffBehavior.SnapshotAndReplace);
}
And here is a gif of it in action.
I really don't normally like doing homework problems for people, but I'm bored at work so here you go.
You need to just use the Margin and update the Left and Top values to random numbers within the height/width of your grid so it doesn't go outside of the view.
Also, make sure to use IsTabStop="False" otherwise, you could tab to it and still click it.
So, for the simplest case using codebehind:
XAML:
<Window x:Class="mousemove.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 x:Name="Grid1">
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="0,0,0,0" Name="button1" Width="75" MouseEnter="button1_MouseEnter" IsTabStop="False"/>
</Grid>
MainWindow.xaml.cs
private void button1_MouseEnter(object sender, MouseEventArgs e)
{
int maxLeft = Convert.ToInt32(Grid1.ActualWidth - button1.Width);
int maxTop = Convert.ToInt32(Grid1.ActualHeight - button1.Height);
Random rand = new Random();
button1.Margin = new Thickness(rand.Next(maxLeft), rand.Next(maxTop), 0, 0);
}
I'm developing a Windows Phone 8.1 app with a MapControl as the main focal point of my app. I basically want to integrate a similar experience design wise as the Nokia Here Maps App.
The bottom black Frame can be pulled upwards to reveal its content.
How am I able to do this?
EDIT
I now have:
ExtraInfo.RenderTransform = new TranslateTransform();
ExtraInfo.ManipulationDelta += OnManipulationDelta;
ExtraInfo.ManipulationMode = Windows.UI.Xaml.Input.ManipulationModes.TranslateY;
in my constructor
The eventhandler
private void OnManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
{
try
{
Storyboard myStoryboard = (Storyboard)this.Resources["TestStoryboard"];
TranslateTransform myTranslate = new TranslateTransform();
myTranslate.Y = e.Delta.Translation.Y;
ExtraInfo.RenderTransform = myTranslate;
Storyboard.SetTarget(myStoryboard.Children[0] as DoubleAnimation, ExtraInfo);
myStoryboard.Begin();
}
catch (Exception)
{
Debug.WriteLine("Animation called");
}
}
My XAML
<Grid>
<StackPanel>
<Maps:MapControl Height="{Binding ActualHeight, ElementName=pageRoot}" x:Name="Map" LandmarksVisible="False" ZoomLevel="{Binding zoomlevel, Mode=TwoWay, FallbackValue=8}" MapServiceToken="#######" TrafficFlowVisible="True">
<Image x:Name="NewCheckImage" Visibility="Collapsed" Maps:MapControl.Location="{Binding Center, ElementName=Map}" Maps:MapControl.NormalizedAnchorPoint=".5,.5"></Image>
</Maps:MapControl>
<StackPanel x:Name="ExtraInfo" Height="{Binding ActualHeight, ElementName=pageRoot}" Background="Black" Margin="0,-250,0,0" Canvas.ZIndex="1">
<StackPanel.RenderTransform>
<TranslateTransform X="0" Y="0">
</TranslateTransform>
</StackPanel.RenderTransform>
</StackPanel>
</StackPanel>
</Grid>
The storyboard
<Page.Resources>
<Storyboard x:Key="TestStoryboard">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"
To="0">
</DoubleAnimation>
</Storyboard>
</Page.Resources>
But the panel does some jumpy unpredictable moves.
Set RenderTransform = new TranslateTransform() for your StackPanel, handle Page ManipulationDelta event and there change your (StackPanel.RenderTransform as TranslateTransform).Y value. Use Storyboard (animation on RenderTransform) to achieve effect of pulling up/down on touch release.
You should do something like this: (pseudocode)
void OnStackPanelLoaded(..)
{
StackPanel.VerticalAlingment = VerticalAlingment.Bottom;
StackPanel.RenderTransform = new TranslateTransform(0, StackPanel.ActualHeight);
// translate stackpanel out of page, be sure stackpanel has bottom alingment of grid
}
void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs args)
{
var stackPanelTransform = MyStackPanel.RenderTransform as TranslateTransform;
stackPanelTransform.Y += args.Delta.Translation.Y;
}
You should also register to ManipulationCompleted event and there run close/open animation.
void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs args)
{
// you can also add additional logic here to reverse open/close logic
// for example: if user touch translation delta is too small then instead of opening panel - close it like in Here
if (IsPanelOpened())
Resources["ClosePanelStoryboard"].BeginStoryboard();
else
Resources["OpenPanelStoryboard"].BeginStoryboard();
}
bool IsPanelOpened()
{
var translateTransform = myStackPanel.RenderTransform as TranslateTransform;
return translateTransform.Y > double.Epsilon;
}
Check DoubleAnimation.EasingFunction - to achieve same animation effect like in Here Drive (animated open/close).
Implementing good panel takes time. Initially it should have Opacity = 0, IsHitTest = false (because it can blink on page start - before RenderTransform is set) - you should also register to SizeChanged event in StackPanel to ensure that TranslateTransform is set correctly (sometimes you can get StackPanel.ActualHeight value of 0 - most likely in release build because they're faster).
I've been teaching myself WPF through Sells/Griffiths' "Programming WPF", and I've found it a great resource, but I'm trying to take some of the concepts they've introduced me to and go a step further, and I'm running into a conceptual snag on how to put the pieces together to accomplish what I'm trying to do.
In this exercise, I'm trying to create self-terminating animations; FrameworkElements that are created by Events, perform an animation, and then delete themselves. I'm having problems figuring out how to call back to the parent FrameworkElement from the animation.Completed event.
I asked this question originally just using DoubleAnimations that were uncontained and not part of the Storyboard. I have since added the Storyboard, and made the Storyboard and rectangle resources so they can be reused easily.
Here's what I have so far:
.xaml:
<Window.Resources>
<Storyboard x:Key="GrowSquare" x:Shared="False">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="-50" Duration="0:0:2"/>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="-50" Duration="0:0:2"/>
<DoubleAnimation Storyboard.TargetProperty="(Ellipse.Width)" By="100" Duration="0:0:2"/>
<DoubleAnimation Storyboard.TargetProperty="(Ellipse.Height)" By="100" Duration="0:0:2"/>
</Storyboard>
<Rectangle x:Key="MyRect" x:Shared="False" Width="20" Height="20">
</Rectangle>
</Window.Resources>
<Canvas x:Name="myCanvas" MouseMove="myCanvas_MouseMove" Background="White"/>
.cs:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
lastFire = DateTime.Now;
}
DateTime lastFire;
private void myCanvas_MouseMove(object sender, MouseEventArgs e)
{
DateTime nowTime = DateTime.Now;
TimeSpan T = nowTime.Subtract(lastFire);
if (T.TotalMilliseconds > 200)
{
lastFire = nowTime;
Random Rand = new Random();
Rectangle myRect = (Rectangle)FindResource("MyRect");
myRect.Fill = new SolidColorBrush(Color.FromRgb((byte)Rand.Next(256), (byte)Rand.Next(256), (byte)Rand.Next(256)));
Point myLoc = e.GetPosition(myCanvas);
Canvas.SetLeft(myRect, myLoc.X - 10);
Canvas.SetTop(myRect, myLoc.Y - 10);
myCanvas.Children.Add(myRect);
Storyboard SB = (Storyboard)FindResource("GrowSquare");
SB.Completed += new EventHandler(SB_Completed);
SB.Begin(myRect);
}
}
void SB_Completed(object sender, EventArgs e)
{
myCanvas.Children.RemoveAt(0);
}
}
This works, but not in the way I'd like it. Since the canvas is empty, and all animations are the same length, when an animation finishes, it will always be the one that was called on the first child of the canvas.
However, I'd like to implement animaitons that take a random amount of time, meaning that animations will not always start and end in the same order. Somehow in the SB_Completed event, I'd like to access the control that it was being called upon, but I can't seem to find the path to it yet.
Is there a way to get from the Media.Animation.ClockGroup that calls the SB_Completed event to the control that the animation is being called on?
change the line where you assign the event handler to this:
SB.Completed += (s,e) => myCanvas.Children.Remove(myRect);