WPF Animation is Choppy - c#

I am trying to use some animations to make my application feel good. But I could not help the choppy animation that no matter what I do, it is always end up stuttering.
Take a look:
DoubleAnimation anim = new DoubleAnimation()
{
//ht is height of DockPanel, I wanted to start from 200 less than Actual DockPanel Height
From = ht - 200,
To = ht,
Duration = TimeSpan.FromSeconds(1),
AccelerationRatio = 0.5,
DecelerationRatio = 0.5
};
x.BeginAnimation(HeightProperty, anim);
//x is the UserControl
Also, what I need to animate is a custom UserControl, which contains some text like 100 words and bunch of Images. I just want to make it grow in to the height of the current DockPanel as soon it is loaded.
What I saw by searching for the solution is this,
Timeline.SetDesiredFrameRate(anim, 10);
Even trying any value in there nothing really happens.

Framerate is like the frame rate of a film.
A low frame rate will give you a choppy film or a choppy animation.
Using a dockpanel is probably a bad idea for some content you are going to animate, because it will try and adjust things every time your height changes.
I suggest you go with a grid instead.
You should use a scaletransform. Partly because as you animate height you will find all the content of your usercontrol have their measures invalidated and they will want to start off the whole measure arrange cycle many times.
If you're thinking measure arrange? Then read up on how the wpf layout system works.
I would also advise you to use xaml rather than code.
Here's some code to think about:
private void StartAnimate_Click(object sender, RoutedEventArgs e)
{
var tran = testRectangle.RenderTransform = new ScaleTransform(1d, 1d)
{
CenterX = 0.5d,
CenterY = 0.5d
};
var anim = new DoubleAnimation
{
To = 1.0,
From=0,
Duration = TimeSpan.FromSeconds(0.5d),
DecelerationRatio = 0.5d,
FillBehavior = FillBehavior.Stop
};
tran.BeginAnimation(
ScaleTransform.ScaleYProperty,
anim,
HandoffBehavior.Compose);
}
I put a rectangle and my button in a dockpanel to prove this works.
<DockPanel>
<Rectangle Fill="Blue" Name="testRectangle"
Width="500"
/>
<Button Content="Animate"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Name="StartAnimate"
Click="StartAnimate_Click" />
</DockPanel>
A rectangle is pretty simple but it animates smoothly.

Related

uwp Expression node fade in animation

I am using the sample on Windows UI dev labs sample gallery. And I used ShyHeader example to put in my app, but I am not using exactly the same code but I actually edited the example according to own needs.
My question is how can I use expression node to fade in a specific XAML element in correspondence to the scroll viewer, I am able to fade out an element with the scroll viewer. but I am not being able to fade in an element from opacity 0 -> opacity 1.
here is my code.
<ScrollViewer x:Name="MyScrollViewer">
<Grid>
<local:MyAdaptiveView Margin="0,300,0,0"
x:Name="AllVideosGridView"/>
<Grid x:Name="Header" Height="300" VerticalAlignment="Top">
<FlipView x:Name="MainFlipView"
</FlipView>
<Grid Background="Blue" Height="150" VerticalAlignment="Bottom" Opacity="0.5" Name="FrontGrid">
</Grid>
</Grid>
</Grid>
</ScrollViewer>
page loaded method
the only important piece of code is only at the very end of this method, the last 4, 5 lines, you can see I am able to fade out element by doing 1- progresNode but my attempt to fade in another element ( frontVisual ) by doing 0+ progressNode doesn't work and frontVisual actually remains at 0 opacity even after I scroll.
private void ShyView_Loaded(object sender, RoutedEventArgs e)
{
// Get the PropertySet that contains the scroll values from MyScrollViewer
_scrollerPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(MyScrollViewer);
_compositor = _scrollerPropertySet.Compositor;
// Create a PropertySet that has values to be referenced in the ExpressionAnimations below
_props = _compositor.CreatePropertySet();
_props.InsertScalar("progress", 0);
_props.InsertScalar("clampSize", 150);
_props.InsertScalar("scaleFactor", 0.7f);
// Get references to our property sets for use with ExpressionNodes
var scrollingProperties = _scrollerPropertySet.GetSpecializedReference<ManipulationPropertySetReferenceNode>();
var props = _props.GetReference();
var progressNode = props.GetScalarProperty("progress");
var clampSizeNode = props.GetScalarProperty("clampSize");
var scaleFactorNode = props.GetScalarProperty("scaleFactor");
// Create a blur effect to be animated based on scroll position
var blurEffect = new GaussianBlurEffect()
{
Name = "blur",
BlurAmount = 0.0f,
BorderMode = EffectBorderMode.Hard,
Optimization = EffectOptimization.Balanced,
Source = new CompositionEffectSourceParameter("source")
};
var blurBrush = _compositor.CreateEffectFactory(
blurEffect,
new[] { "blur.BlurAmount" })
.CreateBrush();
blurBrush.SetSourceParameter("source", _compositor.CreateBackdropBrush());
// Create a Visual for applying the blur effect
_blurredBackgroundImageVisual = _compositor.CreateSpriteVisual();
_blurredBackgroundImageVisual.Brush = blurBrush;
_blurredBackgroundImageVisual.Size = new Vector2((float)Header.ActualWidth, (float)Header.ActualHeight);
// Insert the blur visual at the right point in the Visual Tree
ElementCompositionPreview.SetElementChildVisual(Header, _blurredBackgroundImageVisual);
// Create and start an ExpressionAnimation to track scroll progress over the desired distance
ExpressionNode progressAnimation = EF.Clamp(-scrollingProperties.Translation.Y / clampSizeNode, 0, 1);
_props.StartAnimation("progress", progressAnimation);
// Create and start an ExpressionAnimation to animate blur radius between 0 and 15 based on progress
ExpressionNode blurAnimation = EF.Lerp(0, 15, progressNode);
_blurredBackgroundImageVisual.Brush.Properties.StartAnimation("blur.BlurAmount", blurAnimation);
// Get the backing visual for the header so that its properties can be animated
Visual headerVisual = ElementCompositionPreview.GetElementVisual(Header);
// Create and start an ExpressionAnimation to clamp the header's offset to keep it onscreen
ExpressionNode headerTranslationAnimation = EF.Conditional(progressNode < 1, 0, -scrollingProperties.Translation.Y - clampSizeNode);
headerVisual.StartAnimation("Offset.Y", headerTranslationAnimation);
// Create and start an ExpressionAnimation to scale the header during overpan
ExpressionNode headerScaleAnimation = EF.Lerp(1, 1.25f, EF.Clamp(scrollingProperties.Translation.Y / 50, 0, 1));
headerVisual.StartAnimation("Scale.X", headerScaleAnimation);
headerVisual.StartAnimation("Scale.Y", headerScaleAnimation);
//Set the header's CenterPoint to ensure the overpan scale looks as desired
headerVisual.CenterPoint = new Vector3((float)(Header.ActualWidth / 2), (float)Header.ActualHeight, 0);
// Get the backing visual for the photo in the header so that its properties can be animated
Visual photoVisual = ElementCompositionPreview.GetElementVisual(MainFlipView);
// Create and start an ExpressionAnimation to opacity fade out the image behind the header
ExpressionNode imageOpacityAnimation = 1 - progressNode;
photoVisual.StartAnimation("opacity", imageOpacityAnimation);
// Get the front visual for the photo in the header so that its properties can be animated
Visual frontVisual = ElementCompositionPreview.GetElementVisual(FrontGrid);
// Create and start an ExpressionAnimation to opacity fade out the image behind the header
ExpressionNode imageOpacityAnimation2 = 0 + progressNode;
frontVisual.StartAnimation("opacity", imageOpacityAnimation2);
}
Note the behavior I actually want is that when I scroll down then FlipView should fade out and when I scroll up to the top it should fade in, which is working perfectly, but along with it I want FrontGrid to be exactly opposite, i.e: fade in on scroll down and fade out on scroll up.
Thanks in advance
Your expression looks OK.
Note the Opacity you are animating with Composition is the Opacity of Visual. However, the Opacity of 0.5 you are setting on FrontGrid XAML is from UIElement. Doing so will break the Composition opacity expression animation.
The fix is simple - Try getting the Visual of your FrontGrid right after InitializeComponent and set its Opacity to 0.5 there (i.e. frontVisual.Opacity = 0.5) instead of setting it in XAML.
You will see this kind of "weird" behaviors starting from the Anniversary Update, due to a XAML-Composition Interop Behavior change.
For a full explanation, please read this official document.
In short, XAML doesn't know if Composition has changed the Opacity, it still thinks it should be 0.5 as it was last set. So it will try to override and cause the animation to fail. This happens to a few more properties like Offset and Size too.
My advice is if you go Composition, try going Composition all the way. :)

Use of GetValueAtPosition with WPF ToolKit Chart

I'm trying to get the X-coordinate position of the mouse on a Chart. Looking at some other code on this site, I have
private void Chart_MouseMove(object sender, MouseEventArgs e)
{
var chart = (Chart)sender;
var xAxisRange = (IRangeAxis)chart.Axes[0];
var mousePositionInPixels = e.GetPosition(chart);
double mouseXPositionInChartUnits = (double)xAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.X, Unit.Pixels));
TextBoxR0.Text = mouseXPositionInChartUnits.ToString(); //temp display textBox for debugging
RaisePropertyChanged();
}
The problem is that the values are shifted to the left by a constant. The X-value for the origin is 0, but GetValueAtPosition is returning, say 15, and all x values returned are 15 greater than they should be. If I expand the chart horizontally, the offset changes, becoming smaller. If I contract the chart horizontally, the offset becomes larger. I've done some extensive searching for an answer, and have looked for some member of Chart that may give me a left margin offset, but with no success.
I know this answer is late, but I had the same problem. My solution was to get the mouse position relative to the series instead of the chart. Its not perfect (it’s off by a pixel or 2 at a resolution of 1366 x 720), but when rounded to 2 places it’s good enough for me.
In my xaml I gave the series a name (“Series”, as unoriginal as that is). I also have a TextBlock that's bound to the string Pos, so I can see the position of the cursor. I've bound the ItemsSource of the series to List<KeyValuePair<Double, Double>> Points
Lastly, don’t forget that the y axis is flipped relative to screen coordinates (the y axis increases as you move up, but screen coordinates increase as you move down), so that’s why there's a 1.0 – yAxisRange in there.
Xaml:
<chartingToolkit:Chart Name="columnChart" MouseMove="Chart_MouseMove">
<chartingToolkit:LineSeries Name="Series" ItemsSource="{Binding Points}" DependentValuePath="Value" IndependentValuePath="Key" />
</chartingToolkit:Chart>
Code:
private void Chart_MouseMove(object sender, MouseEventArgs e)
{
var chart = (Chart)sender;
var mousePositionInPixels = e.GetPosition(Series);
var xAxisRange = (IRangeAxis)chart.ActualAxes[0];
double mouseXPositionInChartUnits = (double)xAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.X, Unit.Pixels));
var yAxisRange = (IRangeAxis)chart.ActualAxes[1];
double mouseYPositionInChartUnits = 1.0 - (double)yAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.Y, Unit.Pixels));
Pos = $"({Math.Round(mouseXPositionInChartUnits, 2)}, {Math.Round(mouseYPositionInChartUnits, 2)})";
}

WPF Path's Geometry Transform performance gets worse with LineGeometries' length increase

I'm currently trying to create a little plot interactive editor, using WPF.
On maximized window the plot dragging with mouse is not responsive enough because of the plot grid.
I got a path for my plot grid lying inside a Canvas control (render transform just shifts it to the bottom of the canvas)
<Path Name="VisualGrid" RenderTransform="{StaticResource PlotTechnicalAdjust}" Style="{DynamicResource ResourceKey=GridStyle}" Panel.ZIndex="1"/>
Here is how grid is created; _curState has actual camera "viewport" metadata
if (_curState.Changes.ScaleStepXChanged)
{
foreach (TextBlock item in _xLabels)
{
DeleteLabel(item);
}
_xLabels.Clear();
double i = _curState.LeftEdgeLine;
_gridGeom.Children[(int)GridGeomIndexes.VerticalLines] = new GeometryGroup { Transform = _verticalLinesShift};
var verticalLines =(GeometryGroup)_gridGeom.Children[(int)GridGeomIndexes.VerticalLines];
while (i <= _curState.RightEdgeLine * (1.001))
{
verticalLines.Children.Add(new LineGeometry(new Point(i * _plotParameters.PixelsPerOneX, 0),
new Point(i * _plotParameters.PixelsPerOneX,
-_wnd.ContainerGeneral.Height)));
_xLabels.Add(CreateLabel(i, Axis.X));
i += _curState.CurrentScaleStepX;
}
_curState.Changes.ScaleStepXChanged = false;
}
if (_curState.Changes.ScaleStepYChanged)
{
foreach (TextBlock item in _yLabels)
{
DeleteLabel(item);
}
_yLabels.Clear();
double i = _curState.BottomEdgeLine;
_gridGeom.Children[(int)GridGeomIndexes.HorizontalLines] = new GeometryGroup { Transform = _horizontalLinesShift};
var horizontalLines = (GeometryGroup)_gridGeom.Children[(int)GridGeomIndexes.HorizontalLines];
while (i <= _curState.TopEdgeLine * (1.001))
{
horizontalLines.Children.Add(new LineGeometry(new Point(0, -i * _plotParameters.PixelsPerOneY),
new Point(_wnd.ContainerGeneral.Width,
-i * _plotParameters.PixelsPerOneY)));
_yLabels.Add(CreateLabel(i, Axis.Y));
i += _curState.CurrentScaleStepY;
}
_curState.Changes.ScaleStepYChanged = false;
}
Where Transforms are composition of TranslateTransform and ScaleTransform (for vertical lines I only use X components and only Y for horizontal lines).
After beeing created those GeometryGroups are only edited if a new line apears into camera or an existing line exits viewable space. Grid is only recreated when axis graduations have to be changed after zooming.
I have a dragging option implemented like this:
private Point _cursorOldPos = new Point();
private void OnDragPlotMouseMove(object sender, MouseEventArgs e)
{
if (e.Handled)
return;
Point cursorNewPos = e.GetPosition(ContainerGeneral);
_plotView.TranslateShiftX.X += cursorNewPos.X - _cursorOldPos.X;
_plotView.TranslateShiftY.Y += cursorNewPos.Y - _cursorOldPos.Y;
_cursorOldPos = cursorNewPos;
e.Handled = true;
}
This works perfectly smooth with a small window (1200x400 units) for a large amount of points (like 100+).
But for a large window (fullscreen 1920x1080) it happens pretty jittery even without any data-point controls on canvas.
The strange moment is that lags don't appear when I order my GridGenerator to keep around 100+ lines for small window and drag performance suffers when I got less than 50 lines on maximezed. It makes me think that it might somehow depend not on a number of elements inside a geometry, but on their linear size.
I suppose I should mention that OnSizeChanged I adjust the ContainerGeneral canvas' height and width and simply re-create the grid.
Checked the number of lines stored in runtime to make sure I don't have any extras. Tried using Image with DrawingVisual instead of Path. Nothing helped.
Appearances for clearer understanding
It was all about stroke dashes and WPF's unhealthy desire to count them all while getting hit test bounds for DrawingContext.
The related topic is Why does use of pens with dash patterns cause huge (!) performance degredation in WPF custom 2D drawing?

WPF fade in animation disapears after finish

I have created fade animation for my icon, but when animation finished the icons sets back to alpha 0. How can I get him to stay in 1?
var a = new DoubleAnimation
{
From = 0.0,
To = 1.0,
FillBehavior = FillBehavior.Stop,
Duration = new Duration(TimeSpan.FromSeconds(1))
};
var storyboard = new Storyboard();
storyboard.Children.Add(a);
Storyboard.SetTarget(a, IconGlass);
Storyboard.SetTargetProperty(a, new PropertyPath(OpacityProperty));
storyboard.Begin();
}
XAML:
<StackPanel Name="IconGlass" Orientation="Horizontal" Margin="0,0,0,39" Opacity="0">
this occurs because in your StackPanel you have the Property Opacity set to 0. What you are doing is,
You have a StackPanel with Opacity 0.
You add a Animation to your StackPanel to grow up the opacity.
But after finish the animation, StackPanel will have his default value of Opacity, that is 0.
its not the best way, but you can remove Opacity="0" and Add Visibility="Collapsed"
And when you animation start put your StackPanel Visible.
IconGlass.Visibility = Visibility.Visible;
So when finishing the animation, will no desapear the StackPanel.
Know that this is not the best practise to do, but maybe one of the simplest or fastest way.
I hope this help. greetings!

Non-smooth DoubleAnimation

I am using DoubleAnimation for zooming and panning in and out of map. My map is an image with huge resolution (15,000 x 8,438). The problem is that on first time the zoom animation is very faltering and not smooth, at second time it`s getting better and so on. How can I make my animation smoother or make some cashing of the image or animation before performing it, or maybe using other form of animation?
My Code:
namespace AnimationTest
{
public partial class MainWindow : Window
{
ScaleTransform transP;
TranslateTransform trans2P;
DoubleAnimation animP;
DoubleAnimation animYP;
DoubleAnimation animXP;
TransformGroup myTransformGroupP;
public MainWindow()
{
InitializeComponent();
transP = new ScaleTransform();
trans2P = new TranslateTransform();
myTransformGroupP = new TransformGroup();
myTransformGroupP.Children.Add(transP);
myTransformGroupP.Children.Add(trans2P);
animP = new DoubleAnimation(1, 20, TimeSpan.FromMilliseconds(3000));
animXP = new DoubleAnimation(0, -14000, TimeSpan.FromMilliseconds(3000));
animYP = new DoubleAnimation(0, -4000, TimeSpan.FromMilliseconds(3000));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
image1.RenderTransform = myTransformGroupP;
transP.BeginAnimation(ScaleTransform.ScaleXProperty, animP);
transP.BeginAnimation(ScaleTransform.ScaleYProperty, animP);
trans2P.BeginAnimation(TranslateTransform.XProperty, animXP);
trans2P.BeginAnimation(TranslateTransform.YProperty, animYP);
}
}
}
I have not tried your animation approach, i tried to implement my own logic to to this.
First i am inspired by zooming animation used by Picasa. So i tried to implement similar type of animation and this works fine for me on my core2duo processor with image size of 10000x5000 without any lag.
This approach consumed a lot of memory, but when i compared my memory usage with Picasa ImageViewer it was almost same. This approach may increase the loading time of your application but this can be handled and not a problem here.
Here is the Code for Main Window Grid that i have Used.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="30" Width="100" Content="Zoom" Click="ButtonZoom_OnClick" />
<Image RenderOptions.BitmapScalingMode="HighQuality" Stretch="Uniform" Width="100" Height="100" Grid.Row="1"
Margin="30" VerticalAlignment="Center" HorizontalAlignment="Center" Source="mad1.jpg" Name="ImageMain"
x:FieldModifier="private" />
</Grid>
Button Click event Code
private void ButtonZoom_OnClick(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
var i = 0;
while (i++ < 100)
{
var i1 = i;
//var i1 = (-0.00092)*(i*i) + (0.092)*i + 0.2;
Dispatcher.Invoke(new Action(() =>
{
if (i1 < 10 || i1 > 90)
{
ImageMain.Height += 0.5;
ImageMain.Width += 0.5;
}
else if (i1 < 30 || i1 > 70)
{
ImageMain.Height += 1;
ImageMain.Width += 1;
}
else
{
ImageMain.Height += 3;
ImageMain.Width += 3;
}
}));
Thread.Sleep(30);
}
});
}
The commented line in this code is a quadratic equation for a smooth animation for acceleration and acceleration of animation. the roots are calculated for starting zooming by 0.2 and half at 2.5 and stops at 0.2 with in range of [0-100]. if you want to create your fully customized animation you may use WolframAlpha to check your animation graph. but the simple approach is to use simple control statements to control your animation.
This code is only for zooming your image, your approach will be similar for zoom out.
Have you looked into Microsoft's DeepZoom technology (this is what they use for Bing Maps)? http://msdn.microsoft.com/en-us/library/cc645050(v=vs.95).aspx#deep_zoom_examples
Since you have not shown any XAML I'll try from the most basic - try to reduce bitmap scaling mode with RenderOptions.BitmapScalingMode="LowQuality" on your image element like this:
<Image x:Name="image1"
Source="huge-image.jpg"
Stretch="Uniform"
RenderOptions.BitmapScalingMode="LowQuality" />
Note, that this is only actual if you targeting .NET 3.0-3.5 since starting from .NET 4.0 the "LowQuality" setting is already set as default, so you have no need to specify it explicitly. But if your zoom-in animation still faltering you could try to change this default scaling from LowQuality to even more lower NearestNeighbor which, according to documentation:
...provides performance benefits over LowQuality mode when the software rasterizer is used. This mode is often used to magnify a bitmap.
Also since you are about to show large image with some loss of quality it may be better to specify UseLayoutRounding="True" on your image or it parent element to improve image quality.
You want to use a cached composition. Render your map but assign a BitmapCache to the CacheMode property and set the RenderAtScale to a value larger than 1. If you zoom into your map 5x you should use the RenderAtScale with this value as it caches the image for this type of zoom.
This may result in a much higher memory consumption but may smooth the scrolling.
Further more Nolonar may be right. You may need to create mipmaps for the image and provide tile rendering to partially load seen tiles as your image is quite large.

Categories

Resources