Get smooth animations using #wpf? - c#

I am developing a simple graph viewer. I got it rendered nicely using simple Ellipses and Lines on a canvas.
Now, I want to have some dots navigating the graph. You could imagine it as cars traversing the streets of a city.
To have something that could run the animation logic at a certain rate I setup a DispatcherTimer and did all the animation logic there. It works, but it is really choppy. To be true, I am not surprised. I understand that this way of rendering onto a canvas and animate using the event system is not something you could expect to run as smooth as a 2D game engine for example.
So, my question is, being a newbie to WPF, is there something I could do to improve the performance/accuracy? I have seen there exist a Skia package available for c# (SkiaSharp) that I think could improve the rendering performance but what about running the logic at an "accurate" (no realtime os here) frame rate?

Performance was not one of WPFs primary design goals. If you need high-speed custom animations that can't be implemented with the WPF animation system then you're better off deriving from FrameworkElement directly and overriding OnRender:
public class CustomControl : FrameworkElement
{
public CustomControl()
{
// leave this line in to force a render of the control for each frame
CompositionTarget.Rendering += (s, e) => InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
var angle = Math.PI * DateTime.Now.Ticks / 10000000;
var xoffset = 100 + (int)(50 * Math.Sin(angle));
var yoffset = 100 + (int)(50 * Math.Cos(angle));
drawingContext.DrawEllipse(Brushes.Red, null, new Point(xoffset, yoffset), 50, 50);
}
}

It is necessary to use Animatios to display data in the required manner. WPF Animations are meant to be used for smooth rendering and demonstrate better performance when dispatcher.
In the simplest case you will need to start a Double animation to update a single property of your view model. Then recalculate properties (size, positions, etc) of different objects based on this value.
Another possible solution is to define a custom animation class. This solution is demonstrated in WPF Tutorial - Part 2 : Writing a custom animation class. This solution provides more flexibility, but generally you will get a similar value (you can consider it to be "time ellapsed") that can be used to calculate objects settings.

There's a special mechanism in WPF just for your case - Animations. I'd recommend you to read this article: How to: Animate an Object Along a Path.
Below is an example of traversing a simple graph. Also note that in the example I use one predefined path (for simplicity). In practice you can create such paths and animations dynamically from code-behind.
<Canvas>
<Canvas.Resources>
<PathGeometry x:Key="AnimationPath" Figures="M 100,100 L 200,150 L 230,250 L 150,300 L 100,100"/>
</Canvas.Resources>
<Line X1="115" X2="215" Y1="115" Y2="165" Stroke="Green" StrokeThickness="5"/>
<Line X1="215" X2="245" Y1="165" Y2="265" Stroke="Green" StrokeThickness="5"/>
<Line X1="245" X2="165" Y1="265" Y2="315" Stroke="Green" StrokeThickness="5"/>
<Line X1="165" X2="115" Y1="315" Y2="115" Stroke="Green" StrokeThickness="5"/>
<Ellipse Width="30" Height="30" Canvas.Left="100" Canvas.Top="100" Fill="Red"/>
<Ellipse Width="30" Height="30" Canvas.Left="200" Canvas.Top="150" Fill="Red"/>
<Ellipse Width="30" Height="30" Canvas.Left="230" Canvas.Top="250" Fill="Red"/>
<Ellipse Width="30" Height="30" Canvas.Left="150" Canvas.Top="300" Fill="Red"/>
<Ellipse x:Name="traveller" Width="30" Height="30" Fill="Blue">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="transform"/>
</Ellipse.RenderTransform>
</Ellipse>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimationUsingPath PathGeometry="{StaticResource AnimationPath}"
Storyboard.TargetName="transform"
Storyboard.TargetProperty="X"
Source="X"
Duration="0:0:10"/>
<DoubleAnimationUsingPath PathGeometry="{StaticResource AnimationPath}"
Storyboard.TargetName="transform"
Storyboard.TargetProperty="Y"
Source="Y"
Duration="0:0:10"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
</Canvas>

Related

How to override all Shapes in a specific area WPF

Currently I am filling my MainWindow with a slightly transparent black:
But I want it to have a "hole" where this effect doesn't take place which should look like the following:
So this needs to be done at runtime since the area which the hole represents is going to change multiple times while the program is running.
What I thought I could do
So at first I thought I could just cut the area in the middle out
like you could do with a Graphics object, but the slightly
transparent black is nothing but a rectangle which is added as a child on a canvas which is currently done like this:
var background = new System.Windows.Shapes.Rectangle
{
Fill = new SolidColorBrush(System.Windows.Media.Color.FromArgb(150, 0, 0, 0)),
Width = ScreenInfo.Width,
Height = ScreenInfo.Height
};
MainCanvas.Children.Add(background);
But I couldn't fine any way to achieve this cut effect.
Creating 4 Rectangles which would look something like this: but this way of doing it didn't seem to me as the most effecient way of achieving this.
Thanks for any kind of help!
Create a CombinedGeometry by cutting a smaller square out of a larger one and then use that with a path. How you size it will depend on your application, a Viewbox will probably be good enough for most cases:
<Grid>
<TextBlock Text="Hello World!" FontSize="200" Foreground="Red" TextWrapping="Wrap" TextAlignment="Center"/>
<Viewbox Stretch="UniformToFill">
<Path Fill="#C0000000">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="0,0,4,4" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry x:Name="cutRect" Rect="1,1,2,2" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
</Viewbox>
</Grid>
Then to change the size of the inner geometry you can either bind its Rect to a view model property or change it directly in code-behind:
cutRect.Rect = new Rect(1, 1, 1, 1);

Drop shadow effect in Windows Phone 8.1 Runtime?

I'm looking for a way to add a Drop Shadow Effect to the multiple kind of elements in my Windows Phone 8.1 Runtime (not Silverlight!) application. The main problem is that.. there's no offical API for it. The main problem is that I need to mimic this effect not only to the basic shapes (like rectangle or a line), but also a path, like here:
Picture is borrowed from this question: path-with-broken-shadow-effect - I hope the owner won't mind ;) Now, he has achieved this effect because it was done in WPF. I'm working on a Universal App (so WinRT), and there's no Effects extension.
I've searched the web multiple times, and found some kind of workarounds, but they all miss something. For example this one:
http://www.silverlightshow.net/items/Simple-Xaml-Drop-Shadows-in-Silverlight-2.aspx <- I can't work on Canvas, the content has to be a Grid.
Do you any idea how can I achieve satisfying results on faking Drop Shadow Effect in Windows Phone 8.1 Runtime?
Apply a RenderTransform to the shadow shape. Set the scale to make it bigger:
<Grid Style="{StaticResource LayoutRootStyle}" Background="#FF803535" >
<Rectangle Width="100" Height="100" Opacity="0.3" RenderTransformOrigin="0,0" StrokeThickness="16" StrokeDashCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" StrokeStartLineCap="Round" Stroke="Black" >
<Rectangle.RenderTransform>
<CompositeTransform ScaleX="1.07" ScaleY="1.07" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Width="100" Height="100" Fill="Blue"></Rectangle>
</Grid>

Two Image Layers and OpacityMask

I'm trying to crop a circle from one image, and put it on top another image in WPF.
The Circle's center changes according to the mouse movements, and needs to be bounded dynamically.
I tried to position two images on top of each other, and use a third image that I draw in real time as an opacity mask.
Could you please provide short code to solve this problem efficiently ?
The code below describes what you can do with an OpacityMask. It's a little counterintuitive, because we expect a XAML rendering to layer elements bottom-to-top.
However, in this case you want your "background" image to layer on top of the foreground, because the OpacityMask will serve to display only that portion of the foreground described by the position and size of the VisualBrush, rendering the rest transparent. It's given as follows:
<Grid x:Name="MainGrid" MouseMove="Grid_MouseMove">
<Rectangle Fill="Red" ></Rectangle>
<Rectangle Fill="Green">
<Rectangle.OpacityMask>
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Ellipse Width="40" Height="40" StrokeThickness="1" Fill="Black" />
</VisualBrush.Visual>
<VisualBrush.RelativeTransform>
<TransformGroup>
<TranslateTransform x:Name="OpacityFilterTransform" X="1" Y="1"/>
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
Then, this event handler code computes the position of the ellipse and applies it to the OpacityFilter's TranslateTransform object, giving you control over the position of the image.
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var height = MainGrid.ActualHeight;
var width = MainGrid.ActualWidth;
// with the position values, interpolate a TranslateTransform for the opacity mask
var transX = position.X / width;
var transY = position.Y / height;
OpacityFilterTransform.X = transX - 0.5;
OpacityFilterTransform.Y = transY - 0.5;
}
This solution should work for any descendant of Visual you care to layer.

Where are the methods of the Graphics class?

I'm trying to reuse a class that I implemented a long time ago in Windows Forms in a new WPF project. This class overrides the OnRender method that uses an object Graphics that draws various objects on the form. Now that I'm using my class in WPF, I have substituted the Graphics object with the DrawingContext object, but some methods of the Graphics class aren't implemented. In particular the methods FillPie, FillEllipse, DrawBeziers are the ones that I need.
Does a class exist that implements these methods? How can I implement these methods?
This is a simple example of how something like this could work in practice.
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="500"
Height="500">
<StackPanel>
<Slider Name="PositionSlider"
Margin="10"
Maximum="50"
Minimum="1"
Value="10" />
<Canvas Width="300" Height="300">
<Path Fill="Gold"
Stroke="Black"
StrokeThickness="1">
<Path.Data>
<EllipseGeometry Center="150,150"
RadiusX="{Binding ElementName=PositionSlider,
Path=Value}"
RadiusY="{Binding ElementName=PositionSlider,
Path=Value}" />
</Path.Data>
</Path>
</Canvas>
</StackPanel>
</Window>
The above WPF shows a simple slider and a circle. The radius of the circle is databound to the value of the slider. As you move the slider the circle because bigger or smaller. In your old world when the slider ( ie. your zoom state changed) you will get a repaint in which you will draw a bigger smaller circle. But in the WPF world this is taken care of by dependency properties and databinding.
The above example is very elementary , you can do more complicated things by doing multibinding bindings with value converters and objects implementing dependency properties and INotifyPropertyChanged all of which is too much to explain in one answer.

Partial scaling of a composed elements in WPF

I am in the following situation: designing an interactive flow-chart GUI. I stuck with animating a scale down animation of the flow-chart. Composed elements of the flow-chart are minimized, but they keep being active.
I have something like this:
<Canvas Canvas.Left="55" Canvas.Top="720" Height="100" Width="500" Tag="stepDown">
<Line Stroke="#99CCFF" StrokeThickness="8" X1="0" X2="720" Y1="10" Y2="10">
<Polygon Stroke="Black" StrokeThickness="2" Points="0,30 40,0 40,60" Canvas.Left="-20" Canvas.Top="-20" Fill="#99CCFF"></Polygon>
<Polygon Stroke="Black" StrokeThickness="2" Points="0,0 0,60 40,30" Canvas.Left="720" Canvas.Top="-20" Fill="#99CCFF"></Polygon>
<Image Canvas.Left="-50" Canvas.Top="-70" Height="53" Name="image32" Source="img/outlet.png" Stretch="Fill" Width="30" Tag="relative" />
</Canvas>
And I would like to shift the whole canvas and its elements to the left - no problem with Translate Transform. Furthermore I would like to scale down only the Line, no problem with Scale Transform.
But (!) at the same time, I want that the Polygons stick to the two line endings of the line. When scaling down only the line, the Polygons, at least one, floats away.
I don't know how to dock these elements or define them at a relative basis. It works fine with a scale down on the whole Canvas, but this changes the Polygon and Images as well.
In order to me you have to define hanchors points, as attached properties. Even define a behavior that track these points accordingly to poligon placements. Then you can bind these property to the line start/end point. In this way line should stay gripped to the polygons. But I did'nt try, is just a design idea.

Categories

Resources