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.
Related
I want to get only one number and one circle when I click the button so if I click the button 3 times I will get the 3 numbers with the circles obviously. It's suppose to be lottery numbers appearing after every click but I have no idea how can I do that.
private void btnGo_Click(object sender, RoutedEventArgs e)
{
Ellipse first = new Ellipse();
first.Fill = new SolidColorBrush(Colors.Red);
first.Height = 70;
first.Width = 70;
first.Margin = new Thickness(50, 100, 0, 0);
caPaper.Children.Add(first);
Ellipse second = new Ellipse();
second.Fill = new SolidColorBrush(Colors.Red);
second.Height = 70;
second.Width = 70;
second.Margin = new Thickness(150, 100, 0, 0);
caPaper.Children.Add(second);
Ellipse third = new Ellipse();
third.Fill = new SolidColorBrush(Colors.Red);
third.Height = 70;
third.Width = 70;
third.Margin = new Thickness(250, 100, 0, 0);
caPaper.Children.Add(third);
Random rd = new Random();
TextBlock txt1 = new TextBlock();
txt1.FontSize = 20;
txt1.Foreground = Brushes.White;
txt1.Text = " " + rd.Next(1, 45);
Canvas.SetTop(txt1, 120);
Canvas.SetLeft(txt1, 70);
caPaper.Children.Add(txt1);
TextBlock txt2 = new TextBlock();
txt2.FontSize = 20;
txt2.Foreground = Brushes.White;
txt2.Text = " " + rd.Next(1, 45);
Canvas.SetTop(txt2, 120);
Canvas.SetLeft(txt2, 170);
caPaper.Children.Add(txt2);
TextBlock txt3 = new TextBlock();
txt3.FontSize = 20;
txt3.Foreground = Brushes.White;
txt3.Text = " " + rd.Next(1, 45);
Canvas.SetTop(txt3, 120);
Canvas.SetLeft(txt3, 270);
caPaper.Children.Add(txt3);
}
private void btnClear_Click(object sender, RoutedEventArgs e)
{
caPaper.Children.Clear();
This is 100% the wrong approach to this.
You should never be dynamically creating UI controls like this. It's so wrong its not worth figuring out what is wrong in your code.
Use MVVM, it will make your life much, much, much easier. Your lottery numbers are data, the balls, a visual representation of that data. So in your VM:
public ObservableCollection<int> Draws {get;} = new ObservableCollection<int>();
// Invoked from command, or could be a click handler until you learn commands
public AddDraw()
{
int draw = GetNextDraw(); //Left as an exercise
Draws.Add(draw)
}
Now that the data is handled, we need to display it. An ItemsControl is how you display collections, use the ItemTemplate to control how the number is displayed (the ball) and ItemsPanel to control the layout (a horizontal WrapPanel is probably what you want):
<ItemsControl ItemsSource="{Binding Draws}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Ellipse Fill="Red/>
<TextBlock Text="{Binding Path=.}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
That's off the top of my head so I may have a property or class wrong. Intellisense should correct it pretty quickly though.
Once its set up you can add margin, change the colors, whatever. This design is what WPF was built for, take advantage of it.
Disclaimer: I have had to deal with creating UI controls at all ONCE. In 7 years of professional WPF work, and only because the data structure being represented was incredibly complex (and not similar to how it was being represented).
As mentioned below, your approach could use a bit of tweaking. WPF has some really powerful data binding features to build UI MUCH MORE pain free. However to answer your question...
It looks as though the only thing that is 'unique' per ball is the margin of the ball and the left value in Canvas.SetLeft
You should be able to derive those values based on the number of children in your canvas. For example something like this:
int marginLeft = 50 + (caPaper.Children.Length * 100);
Ellipse newBall = new Ellipse();
newBall.Fill = new SolidColorBrush(Colors.Red);
newBall.Height = 70;
newBall.Width = 70;
newBall.Margin = new Thickness(marginLeft, 100, 0, 0);
caPaper.Children.Add(newBall);
You'll want to adjust that of course but I wanted to give you an idea to work with. Also, the same will need to be done for the text. Then, each time you click it, it will create them in relation to the number of children it already has.
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);
// ...
I'm trying to make a custom hamburger menu for my app. I have the layout all setup, but, for some reason, my DoubleAnimation isn't working as I expect. I'm trying to accomplish something similar to Cortana's hamburger menu on Windows 10. I'm creating my Storyboard and DoubleAnimation in my code (I still have the same problem even if my animation is created in XAML). Here's what I have:
menuButton.Click += (s, e) => {
if (menuIsOpen) {
Animate(
menuPanel,
"Width",
ActualWidth, //Page.ActualWidth
50,
0.1,
new Duration(new TimeSpan(TimeSpan.TicksPerSecond)));
}
else {
Animate(
menuPanel,
"Width",
50,
ActualWidth, //Page.ActualWidth
0.1,
new Duration(new TimeSpan(TimeSpan.TicksPerSecond)));
}
menuIsOpen = !menuIsOpen;
};
This is the signature for Animate():
private void Animate(DependencyObject item, string property, double from, double to, double? by, Duration duration) {
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
animation.From = from;
animation.To = to;
animation.Duration = duration;
animation.EasingFunction = new SineEase() { EasingMode = EasingMode.EaseInOut };
animation.By = by;
Storyboard.SetTarget(animation, item);
Storyboard.SetTargetProperty(animation, property);
storyboard.Children.Add(animation);
storyboard.Begin();
}
I used a little Debug.WriteLine() to see if the Storyboard is actually running, which it is. The menuPanel's width does not change at all. I've used a similar approach with some of my other projects and it works fine there.
EDIT:
I thought I'd add a little bit more detail.
My app is targeting Windows 10
and I'm able to change the width of menuPanel freely if I don't use Storyboards and DoubleAnimations.
I found this answer on a similar question, and my animation works just fine now.
I figured it out myself. All I had to do was to Enable Dependent Animation (EnableDependentAnimation) on the DoubleAnimation as this animation affects the layout. And then it worked perfectly.
I am attempting to animate the position of a UserControl I have created.
My problem is very similar to this question asked on MSDN, without an answer sadly.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/11ce8aaa-1059-4fe5-8f4d-0fa7978e6ff2/using-pointanimation-to-move-a-control?forum=wpf
To summarize, I have a created a Point type dependency property named 'Position' which I use to specify the UserControl's place on the UI, and would like to animate the UserConrol to a new location.
My problem is a syntax one i believe, i am unsure of the right syntax to target my UserControl property (in code-behind).
public class SubContainer : Control
{
public static readonly DependencyProperty PositionProperty;
static SubContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SubContainer),
new FrameworkPropertyMetadata(typeof(SubContainer)));
PositionProperty = DependencyProperty.Register(
"Position",
typeof(Point),
typeof(SubContainer),
new PropertyMetadata(new Point(0, 0)));
}
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
And then my Animation:
public void BoxTransition()
{
Point destination = new Point(70, 300);
SubContainer box = (MainContent.Children[0] as Container).Children[0] as SubContainer;
PointAnimation transition = new PointAnimation();
TimeSpan timespan = TimeSpan.FromSeconds(2);
this.RegisterName("Target", box.Position);
Transition.From = box.Position;
Transition.To = destination;
Storyboard.SetTargetName(transition, "Target");
Storyboard.SetTargetProperty(transition, new PropertyPath(box.Position));
Storyboard bTransition = new Storyboard();
bTransition.Children.Add(transition);
bTransition.Begin();
}
I get the following error:
Object 'System.Windows.Point' cannot be used as an accessor parameter for a PropertyPath. An accessor parameter must be DependencyProperty, PropertyInfo, or PropertyDescriptor.
Any alternatives to animate my control would also be greatly appreciated!
WPF allows to directly start an animation of a UIElement property without a Storyboard.
You wouldn't need more code than this:
var transition = new PointAnimation
{
To = new Point(70, 300),
Duration = TimeSpan.FromSeconds(2)
};
box.BeginAnimation(SubContainer.PositionProperty, transition);
Note that the above does not set the From property of the PointAnimation. That way the animation will start from the current value of the Position property.
As an alternative to the Position property you may put your SubContainer control in a Canvas
<Canvas>
<local:SubContainer x:Name="box" Canvas.Left="0" Canvas.Top="0" .../>
</Canvas>
and animate its Canvas.Left and Canvas.Top properties:
var duration = TimeSpan.FromSeconds(2);
box.BeginAnimation(Canvas.LeftProperty,
new DoubleAnimation { To = 70, Duration = duration });
box.BeginAnimation(Canvas.TopProperty,
new DoubleAnimation { To = 300, Duration = duration });
OK so problem solved. Use Expression Blend for all your animation needs. The end ;)
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.