I need to make a border slide from the bottom of the screen into view, however i'm having issues getting the ActualHeight of my border control. Because this is animation code i'm putting it in the code-behind as it's the Views responsibility.
My Border has got it's Loaded event tied to this:
private void NotifcationWindow_Loaded(object sender, RoutedEventArgs e)
{
SlideFromBottom(sender);
}
The Sender is the Border object, so the SlideFromBottom method SHOULD be able to use this object and get it's height as it has already been rendered.
public void SlideFromBottom(object sender)
{
//The Notification container
Border notification = (sender as Border);
//The screen size
var workingArea = SystemParameters.WorkArea;
Storyboard sb = new Storyboard();
var animation = new DoubleAnimation()
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(5),
// Get the height and turn it to a negative value, and add screen height.
From = (notification.ActualHeight * -1),
//Slide the border into view
To = notification.ActualHeight
};
Storyboard.SetTarget(animation, notification);
Storyboard.SetTargetProperty(animation, new PropertyPath("(Margin.Bottom)"));
sb.Begin();
}
I'm not receiving any errors, but the animation isn't playing, have I got something wrong? The Border is Vertically Aligned to the bottom, so the negative margin should take it off the screen.
The reason why you're not seeing anything is that you haven't added the animation to the storyboard:
sb.Children.Add(animation);
Then there are some more problems. Such that a part of the margin cannot be animated separately. You would need a ThicknessAnimation.
But there is an easier solution. Use the RenderTransform. If you give your border the following render transform:
<Border>
<Border.RenderTransform>
<TranslateTransform/>
</Border.RenderTransform>
</Border>
, then you can animate it as follows:
// ...
var animation = new DoubleAnimation()
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(5),
From = notification.ActualHeight,
To = 0
};
Storyboard.SetTarget(animation, notification);
Storyboard.SetTargetProperty(animation, new PropertyPath("RenderTransform.Y"));
sb.Children.Add(animation);
// ...
Related
In my UWP App I've got a grid with 2 columns. The App is adaptive and on the mobile I only want to show one column at a time. Is there a way to use animations to reduce the width from column 1 and expand the width from column 2 and the other way round?
Animating size and layout has always been tricky in XAML frameworks. Why? Not because you cannot animate a Width, you can, but the performance usually sucks as a change to the Width/Height automatically triggers layout updates which then do a lot of re-calculating, re-measuring and re-arranging stuff on the UI thread that hurts the performance.
But there's always some workarounds you can do. With Windows Composition API, it's now a lot easier to animate layout changes while maintaining 60 frames per second fresh rate, all thanks to the new API such as ImplicitAnimations, SetImplicitHideAnimation & SetImplicitShowAnimation.
ImplicitAnimations basically allows you to monitor property changes like Opacity, Offset, Size, etc and whenever they are updated, the old value will be animated to the new value smoothly; where SetImplicitHideAnimation & SetImplicitShowAnimation will simply animate when the Visibility of an element is changed. So instead of disappearing instantly, one element can scale down and fade out.
Note you will need to provide your desired animations for the APIs to know how to animate. To make your life a bit easier, I have created some helper methods (see link at the bottom) that encapsulates some key animations that you generally need.
To find out exactly what they do, take a look at the gif below
I am re-positioning, hiding and showing elements in different adaptive visual states, no animation is written in XAML, but with the following code, the Composition API simply takes care of animating all these changes implicitly.
var compositor = this.Visual().Compositor;
// Create background visuals.
var leftBackgroundVisual = compositor.CreateSpriteVisual();
leftBackgroundVisual.Brush = compositor.CreateColorBrush(Colors.Crimson);
LeftGridBackgroundVisualWrapper.SetChildVisual(leftBackgroundVisual);
var middleBackgroundVisual = compositor.CreateSpriteVisual();
middleBackgroundVisual.Brush = compositor.CreateColorBrush(Colors.Gold);
MiddleGridBackgroundVisualWrapper.SetChildVisual(middleBackgroundVisual);
var rightBackgroundVisual = compositor.CreateSpriteVisual();
rightBackgroundVisual.Brush = compositor.CreateColorBrush(Colors.DarkOrchid);
RightGridBackgroundVisualWrapper.SetChildVisual(rightBackgroundVisual);
// Sync background visual dimensions.
LeftGridBackgroundVisualWrapper.SizeChanged += (s, e) => leftBackgroundVisual.Size = e.NewSize.ToVector2();
MiddleGridBackgroundVisualWrapper.SizeChanged += (s, e) => middleBackgroundVisual.Size = e.NewSize.ToVector2();
RightGridBackgroundVisualWrapper.SizeChanged += (s, e) => rightBackgroundVisual.Size = e.NewSize.ToVector2();
// Enable implilcit Offset and Size animations.
LeftText.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
MiddleText.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
RightText.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
LeftGrid.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
MiddleGrid.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
RightGrid.EnableImplicitAnimation(VisualPropertyType.Offset, 400);
leftBackgroundVisual.EnableImplicitAnimation(VisualPropertyType.Size, 400);
middleBackgroundVisual.EnableImplicitAnimation(VisualPropertyType.Size, 400);
rightBackgroundVisual.EnableImplicitAnimation(VisualPropertyType.Size, 400);
// Enable implicit Visible/Collapsed animations.
LeftGrid.EnableFluidVisibilityAnimation(showFromScale: 0.6f, hideToScale: 0.8f, showDuration: 400, hideDuration: 250);
MiddleGrid.EnableFluidVisibilityAnimation(showFromScale: 0.6f, hideToScale: 0.8f, showDelay: 200, showDuration: 400, hideDuration: 250);
RightGrid.EnableFluidVisibilityAnimation(showFromScale: 0.6f, hideToScale: 0.8f, showDelay: 400, showDuration: 400, hideDuration: 250);
There's quite a lot of code so I am not posting everything here. But feel free to check it out from this link.
You can use bind to do it. And you should make two property in Page that code is below.
public static readonly DependencyProperty RcProperty = DependencyProperty.Register(
"Rc", typeof(double), typeof(MainPage), new PropertyMetadata(100d));
public double Rc
{
get { return (double) GetValue(RcProperty); }
set { SetValue(RcProperty, value); }
}
public static readonly DependencyProperty LcProperty = DependencyProperty.Register(
"Lc", typeof(double), typeof(MainPage), new PropertyMetadata(500d));
public double Lc
{
get { return (double) GetValue(LcProperty); }
set { SetValue(LcProperty, value); }
}
But we cant bind double to GridLength that we should add a convert.
public class DoubletoGridConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var n = (double) value;
return new GridLength(n);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
After we wrote it, we can make the Page like below.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{x:Bind Rc,Mode=OneWay,Converter={StaticResource double}}"/>
<RowDefinition Height="{x:Bind Lc,Mode=OneWay,Converter={StaticResource double}}"/>
</Grid.RowDefinitions>
<Grid Background="#FF565656"></Grid>
<Grid Grid.Row="1" Background="#FFa2a2a2"></Grid>
</Grid>
<Button Margin="47,662,0,10" Content="set" Click="Button_OnClick"></Button>
We do the animation when button clicked.
private void Button_OnClick(object sender, RoutedEventArgs e)
{
this.Name = nameof(MainPage);
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
Storyboard.SetTargetName(animation, nameof(MainPage));
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation,"Rc");
animation.EnableDependentAnimation = true;
animation.From = 100;
animation.To = 200;
animation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
storyboard.Children.Add(animation);
storyboard.Begin();
storyboard = new Storyboard();
animation = new DoubleAnimation();
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetName(animation,nameof(MainPage));
Storyboard.SetTargetProperty(animation, nameof(Lc));
animation.From = 500;
animation.To = 150;
animation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
animation.EnableDependentAnimation = true;
storyboard.Children.Add(animation);
storyboard.Begin();
}
I think it can help you.
i am trying to create a animation using grid. it's a login screen. whenever user taps forget password i want the second grid to animate from top and slide till it stops at center and on tap it's visibility changes. i know how to do it using blend but the thing is i hav a compulsion for doing it from code behind. For that i am using doublekeyframe class. Having real trouble in knowing where is the issue in code behind for animating the second grid. Don't know what the issue and how to animate so serious help needed.
here is my code behind:
Grid gd= this.FindName("SecondaryGrid") as Grid;
DoubleAnimationUsingKeyFrames dm=new DoubleAnimationUsingKeyFrames();
LinearDoubleKeyFrame l1=new LinearDoubleKeyFrame();
LinearDoubleKeyFrame l2=new LinearDoubleKeyFrame();
l1.Value=-703.203;
l1.KeyTime=TimeSpan.FromSeconds(0);
l2.Value=0;
l2.KeyTime=TimeSpan.FromSeconds(1);
dm.KeyFrames.Add(l1);
dm.KeyFrames.Add(l2);
dm.Duration=new Duration(TimeSpan.FromMilliseconds(3000));
Storyboard sb = new Storyboard();
sb.Children.Add(dm);
Storyboard.SetTarget(dm, gd);
Storyboard.SetTargetName(dm, gd.Name);
Storyboard.SetTargetProperty(dm, "Position");
sb.Begin();
SecondaryGrid.Visibility = Visibility.Visible;
Grid gd = this.FindName("SecondaryGrid") as Grid;
DoubleAnimationUsingKeyFrames dm = new DoubleAnimationUsingKeyFrames();
LinearDoubleKeyFrame l1 = new LinearDoubleKeyFrame();
LinearDoubleKeyFrame l2 = new LinearDoubleKeyFrame();
l1.Value = -703.203;
l1.KeyTime = TimeSpan.FromSeconds(0);
l2.Value = 0;
l2.KeyTime = TimeSpan.FromSeconds(1);
dm.KeyFrames.Add(l1);
dm.KeyFrames.Add(l2);
dm.Duration = new Duration(TimeSpan.FromMilliseconds(30000));
Storyboard sb = new Storyboard();
sb.Children.Add(dm);
Storyboard.SetTarget(dm, gd);
Storyboard.SetTargetName(dm, gd.Name);
Storyboard.SetTargetProperty(dm, "(UIElement.RenderTransform).(CompositeTransform.TranslateY)");
sb.Begin();
Enjoy :)
EDIT
Well what I did was went to XAML and created a SecondaryGrid now in its properties went to render transform and clicked on edit and set Y value as 1 then I saw in XAML that we get
<Grid.RenderTransform>
<CompositeTransform TranslateX="0" TranslateY="1"/>
</Grid.RenderTransform>
So from this I knew that since there is no position property so I need to set targetProperty as render transform and in that I should Change the Y property since it needs to come from top to bottom which is present inside Composite Transform.TranslateY
I have a WPF application that loads Pages in a templated NavigationWindow. I'd like to implement a slide transition when a new page is loaded and since the window can be resized the target values for the transform need to be determined programatically as far as I am aware.
I tried the following in the NavigationWindow code-behind but it has no effect when it fires. The PageContentContainerTransform is also being correctly located as determined from the debugger.
public void DoTransition()
{
double targetX = this.ActualWidth;
this.TransitionStoryboard.Stop();
this.TransitionStoryboard.Children.Clear();
IEasingFunction easing = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
DoubleAnimation translateXAnim = new DoubleAnimation() {
To = targetX,
Duration = TimeSpan.FromMilliseconds(250),
EasingFunction = easing,
};
DependencyObject d = this.Template.FindName("pageContentContainerTransform", this) as DependencyObject;
Storyboard.SetTarget(translateXAnim, d);
Storyboard.SetTargetProperty(translateXAnim, new PropertyPath(TranslateTransform.XProperty));
this.TransitionStoryboard.Children.Add(translateXAnim);
this.TransitionStoryboard.Begin();
}
The Template is a ControlTemplate containing the following bit of XAML,
...
<ContentPresenter
Grid.Row="1"
x:Name="pageContentContainer"
MaxHeight="{StaticResource ContentWindowMaxHeight}"
MaxWidth="{StaticResource ContentWindowMaxWidth}"
RenderTransformOrigin="0.5,0.5">
<ContentPresenter.RenderTransform>
<TranslateTransform x:Name="pageContentContainerTransform" X="0" Y="0" />
</ContentPresenter.RenderTransform>
</ContentPresenter>
...
Why is there no effect?
Update
The animation works if you animate the element directly without wrapping in a Storyboard object. E.g.
public void DoTransition()
{
double targetX = this.ActualWidth;
this.TransitionStoryboard.Stop();
this.TransitionStoryboard.Children.Clear();
IEasingFunction easing = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
DoubleAnimation translateXAnim = new DoubleAnimation() {
To = targetX,
Duration = TimeSpan.FromMilliseconds(250),
EasingFunction = easing,
};
TranslateTransform t = this.Template.FindName("pageContentContainerTransform", this) as TranslateTransform;
t.BeginAnimation(TranslateTransform.XProperty, translateXAnim);
}
However presumably you miss out on some nice control elements for the animations that the Storyboard object provides e.g. managing the animations (Stop, Start etc.). There appears to be possible arguments to .Begin() on the storyboard object that are pertinent to use within a Template, however calling with .Begin(this, this.Template) also does not do anything.
In the end a combination of factors got it to work. First, use Storyboard.SetTargetName rather than Storyboard.SetTarget. Secondly pass in the template context to the Begin() method. E.g.
public void DoTransition()
{
double targetX = this.ActualWidth;
this.TransitionStoryboard.Stop();
this.TransitionStoryboard.Children.Clear();
IEasingFunction easing = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
DoubleAnimation translateXAnim = new DoubleAnimation() {
To = targetX,
Duration = TimeSpan.FromMilliseconds(250),
EasingFunction = easing,
};
// 1. Refer to the element by Name
Storyboard.SetTargetName(translateXAnim, "pageContentContainerTransform");
Storyboard.SetTargetProperty(translateXAnim, new PropertyPath(TranslateTransform.XProperty));
this.TransitionStoryboard.Children.Add(translateXAnim);
// 2. Pass in the template context here
this.TransitionStoryboard.Begin(this, this.Template);
}
I'm not clear why the SetTargetProperty does not work when you consider that FindName correctly identified the element within the template, but in any case the above methods works.
I cannot get ActualWidth of a canvas to any nonzero value. I simplified the real scenario and devoted a new WPF Application in VS to getting a nonzero value (and understanding). Unfortunately, I'm only getting zero. I feel like I'm missing some basic WPF understanding: I'm not that familiar with WPF.
I copied the MDSN demo, modified it slightly and I now have
public partial class MainWindow : Window {
private Canvas myCanvas;
public MainWindow()
{
InitializeComponent();
CreateAndShowMainWindow();
}
private void CreateAndShowMainWindow()
{
// Create a canvas sized to fill the window
myCanvas = new Canvas();
myCanvas.Background = Brushes.LightSteelBlue;
// Add a "Hello World!" text element to the Canvas
TextBlock txt1 = new TextBlock();
txt1.FontSize = 14;
txt1.Text = "Hello World!";
Canvas.SetTop(txt1, 100);
Canvas.SetLeft(txt1, 10);
myCanvas.Children.Add(txt1);
// Add a second text element to show how absolute positioning works in a Canvas
TextBlock txt2 = new TextBlock();
txt2.FontSize = 22;
txt2.Text = "Isn't absolute positioning handy?";
Canvas.SetTop(txt2, 200);
Canvas.SetLeft(txt2, 75);
myCanvas.Children.Add(txt2);
Grid content = new Grid();
content.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
content.Children.Add(myCanvas);
this.Content = content;
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var canvasRenderWidth = myCanvas.RenderSize.Width;
var canvasActualWidth = myCanvas.ActualWidth;
} //place breakpoint here
I expect the canvas to have the same ActualWidth as the textbox after loading, but at the specified breakpoint, it's zero. Notice that the textboxes are visible when running the code above.
Can someone tell me how to make myCanvas.ActualWidth to automatically become the textbox.ActualWidth or tell me why this shouldn't be done?
In my real usage scenario I've got a Canvas in a in column of a Grid, where the columndefinition's width is set to auto, so I expected it to increase as the canvas' width increases. However, this fails, and I suspect it's due to the canvas.ActualWidth being zero.
Remove this line, and it will work:
content.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Note: The canvas is never resizing itself to fit it's content. The reason why the Text is visible in your example, is that the canvas is not clipping it's content.
when you set
myCanvas.ClipToBounds = true;
the text will also disappear.
The purpose of the code below is that a thumb follows a horizontal mouse movement. The code is called upon a mouse event, so the target value of the animation gets updated continuously.
In the code, offset is the current mouse horizontal position. The problem is, that the animation of the thumb doesn't fully animate to the specified offset, but always seems to be stopping at a value smaller or higher (depending if the mouse is dragged left or right).
The SeekAlignedToLastTick() influences the behavior of the animation, although I couldn't figure out what this function does by reading the documentation.
How can I animate the thumb, so that it follows smoothly the drag event?
private Storyboard _thumbStoryboard;
private DoubleAnimation _thumbAnimation = new DoubleAnimation();;
private CompositeTransform _thumbTransform = new CompositeTransform();
private void UpdateUserInterface(double offset)
{
var thumbItem = Thumb as FrameworkElement;
if (_thumbStoryboard == null)
{
Storyboard.SetTarget(_thumbAnimation, _thumbTransform);
_thumbStoryboard = new Storyboard();
_thumbStoryboard.Children.Add(_thumbAnimation);
thumbItem.RenderTransform = _thumbTransform;
_thumbStoryboard.Duration = new Duration(TimeSpan.FromMilliseconds(100));
_thumbAnimation.EasingFunction = new ExponentialEase();
}
double from = _thumbTransform.TranslateX;
_thumbStoryboard.Stop();
Storyboard.SetTargetProperty(_thumbAnimation, new PropertyPath("TranslateX"));
_thumbAnimation.From = from;
_thumbAnimation.To = offset;
_thumbStoryboard.Begin();
_thumbStoryboard.SeekAlignedToLastTick(TimeSpan.Zero);
}
I've tried to solve your issue, So I've created a Silverlight application and added a Border element for testing.
<Border x:Name="Thumb" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50" height="25" Background="#ff0000" />
There was no need to set the "From" Property, since the DoubleAnimation object could automatically continue from the current Value to the "To" Property.
And you were setting the Duration to the Storyboard, which causes the DoubleAnimation to Cutoff its animation without reaching the "To" Value, You need to set the Duration Property to the DoubleAnimation itself instead.
Also there was no need to call _thumbStoryboard.Stop(), because it will reset the current animation to the first TranslateX Value.
Here is the updated "UpdateUserInterface" function code with comments:
private void UpdateUserInterface(double offset) {
var thumbItem = Thumb as FrameworkElement;
if ( _thumbStoryboard == null ) {
// UpdateLayout Method is update the ActualWidth Properity of the UI Elements
this.UpdateLayout();
// Applying the CompositeTransform on "thumbItem" UI Element
thumbItem.RenderTransform = _thumbTransform;
// Setting the Render Transform Origin to be the Center of X and Y
thumbItem.RenderTransformOrigin = new Point(0.5d, 0.5d);
// Setting the target of the DoubleAnimation to be the Thumb CompositeTransform
Storyboard.SetTarget(_thumbAnimation, _thumbTransform);
// Setting the Targeted Properity of the DoubleAnimation to be The "TranslateX" Properity
Storyboard.SetTargetProperty(_thumbAnimation, new PropertyPath("TranslateX"));
// Used QuinticEase instead of ExponentialEase
// and Added EaseOut to make the animation be more smoother.
_thumbAnimation.EasingFunction = new QuinticEase(){ EasingMode = EasingMode.EaseOut };
// Initializing the Storyboard
_thumbStoryboard = new Storyboard();
// Specifing the Duration of the DoubleAnimation not the StoryBoard
_thumbAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
// Adding the DoubleAnimation to the Children of the Storyboard
_thumbStoryboard.Children.Add(_thumbAnimation);
}
// Calculate the New Centered Position
double newPos = offset - (thumbItem.ActualWidth / 2);
// Set the New DoubleAnimation "To" Value,
// There is no need to set the "From" Value since it'll automatically continue from the current TranslateX Value
_thumbAnimation.To = newPos;
// Begin the animation.
_thumbStoryboard.Begin();
}
Hope that helps you :)
Regards,
Monir Abu Hilal