I have a control that throws events and I need to draw immediately after said events fire. The event fires around 15 times per second with a regular interval, and are handled as expected. I tried the following scenarios:
Scenario 1
In XAML, I created a canvas.
Whenever events from custom control fire, I update a counter. When the CompositionTarget event fires and the counter has changed, the canvas gets redrawn (based on the counter). However, this is too slow. The CompositionTarget event fires at a low speed and is irregular. See:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/how-to-render-on-a-per-frame-interval-using-compositiontarget?view=netframeworkdesktop-4.8
Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?
Scenario 2
I installed the SkiaSharp WPF Nuget packages. Tried basically the same thing as in Scenario 1, this time using the OnPaintSurface event. But I get more-or-less the same results, it seems that OnPaintService is called in a similar way as CompositionTarget.
Demo (notice the irregular updates, sometimes frames hanging or dropping):
Code from demo:
<Window x:Class="TMCVisualizer.AlphaWindow" 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:TMCVisualizer" xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" xmlns:skiasharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" mc:Ignorable="d"
Title="AlphaWindow" Width="1000" Height="480" WindowStyle="SingleBorderWindow" AllowsTransparency="False" ContentRendered="Handle_ContentRendered">
<Grid Background="Beige" x:Name="grid">
<skia:SKElement PaintSurface="OnPaintSurface" IgnorePixelScaling="True" Height="210" VerticalAlignment="Bottom" x:Name="board"></skia:SKElement>
</Grid>
</Window>
//Removed attaching event handlers and such for clarity
private float _index = -1;
private void OnCustomControlEvent(float counter, DateTime dateTime)
{
_index = counter;
this.Dispatcher.Invoke(() =>
{
board.InvalidateVisual();
//Will trigger redraw => OnPaintSurface
});
}
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Transparent);
var paint = new SKPaint
{
Color = SKColors.Black,
IsAntialias = true,
Style = SKPaintStyle.Fill,
TextAlign = SKTextAlign.Center,
TextSize = 24
};
var coord = new SKPoint(e.Info.Width / 2, (e.Info.Height + paint.TextSize) / 2);
canvas.DrawText(_index.ToString(), coord, paint);
}
Also, I tried to directly draw on a writeable bitmap using SkiaSharp, again with the same results. The code I wrote looks similar to this:
The most efficient way to draw in SkiaSharp without using PaintSurface event
Is there any way to draw directly in WPF? Maybe I'm missing something, or maybe I don't fully understand SkiaSharp yet? Is there an alternative package I can use, such as SkiaSharp? I'd hate to say goodbye to WPF, because there are some other WPF components that I need to use in my app.
Edit : What I am trying to achieve:
My "component" that fires the events is a mod music player (SharpMik). I want to draw the notes that are being played in the player on screen (like a music tracker). When using the OnPaintSurface and a regular canvas, I couldn't get a better result than this (when the music is playing at the same time you see that the notes are not properly updated with the beat (slight delays)):
Code for the above result:
<Window x:Class="TMCVisualizer.AlphaWindow" 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:TMCVisualizer" mc:Ignorable="d"
Title="AlphaWindow" Width="1000" Height="480" WindowStyle="SingleBorderWindow" AllowsTransparency="False" ContentRendered="Handle_ContentRendered">
<Canvas x:Name="canvas" Height="210" VerticalAlignment="Bottom" />
</Window>
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using SharpMik;
using SharpMik.Drivers;
using SharpMik.Player;
using SharpMik.Player.Events.EventArgs;
namespace TMCVisualizer
{
public partial class AlphaWindow : Window
{
public Module ModModule;
public MikMod ModPlayer;
private Dictionary<int, List<TextBlock>> _dict = new Dictionary<int, List<TextBlock>>();
public List<Row> Rows;
private string _track = #"C:\Users\Wroah\Documents\MEKX-RMB.S3M";
private float _index = -1F;
public AlphaWindow()
{
InitializeComponent();
((App)Application.Current).WindowPlace.Register(this);
this.Loaded += Handle_AlphaWindow_Loaded;
}
private void Handle_AlphaWindow_Loaded(object sender, RoutedEventArgs e)
{
ModPlayer = new MikMod();
ModPlayer.Init<NaudioDriver>("");
}
private void SetupAndPlay()
{
CompositionTarget.Rendering -= Handle_Tick;
ModModule = ModPlayer.LoadModule(_track);
Rows = ModPlayer.Export(ModModule);
DrawGrid();
//Load again
ModModule = ModPlayer.LoadModule(_track);
this.KeyUp += Handle_AlphaWindow_KeyUp;
CompositionTarget.Rendering += Handle_Tick;
}
private void Handle_AlphaWindow_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
Task.Run(() =>
{
ModPlayer.Play(ModModule);
});
}
}
private void Handle_Tick(object sender, EventArgs e)
{
var index = ModPlayer.GetCurrentIndex();
if (index != _index)
{
_index = index;
UpdateGrid();
}
}
private void Handle_ContentRendered(object sender, EventArgs e)
{
SetupAndPlay();
}
private void UpdateGrid()
{
for (var i = 0; i < 13; i++)
{
var dictIndex = (int)_index;
dictIndex -= 7;
dictIndex += i;
var hasData = dictIndex >= 0;
var list = _dict[i];
var cols = ModModule.numchn;
Row row = null;
if (hasData)
{
row = Rows[dictIndex];
}
for (var j = 0; j <= cols; j++)
{
if (j == 0)
{
//Draw pattern position counter
if (hasData)
list[0].Text = row.Patpos.ToString("D2");
else
list[0].Text = "";
}
else
{
if (hasData)
list[j].Text = row.Cols[j - 1].note;
else
list[j].Text = ".";
}
}
}
}
private void DrawGrid()
{
var xPos = 0;
var yPos = 1;
var width = canvas.ActualWidth;
canvas.Children.Clear();
canvas.Background = Brushes.Black;
for (var i = 0; i < 13; i++)
{
var line = new Line();
line.X1 = xPos;
line.X2 = width;
line.Y1 = yPos;
line.Y2 = yPos;
line.StrokeThickness = (int)1;
line.SnapsToDevicePixels = true;
line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
line.Stroke = Brushes.Gray;
if (i == 6)
{
var brush = new SolidColorBrush(Colors.White);
brush.Opacity = .7;
var rect = new Rectangle();
rect.Width = width;
rect.Height = 15;
rect.Fill = brush;
rect.SnapsToDevicePixels = true;
Canvas.SetLeft(rect, 0);
Canvas.SetTop(rect, yPos);
canvas.Children.Add(rect);
}
canvas.Children.Add(line);
var list = new List<TextBlock>();
for (var j = 0; j < 64; j++)
{
var txt = new TextBlock();
txt.FontFamily = new FontFamily("Consolas");
txt.Width = 30;
txt.Foreground = Brushes.White;
if (i == 6)
txt.Foreground = Brushes.Black;
txt.TextAlignment = TextAlignment.Center;
txt.SnapsToDevicePixels = true;
txt.IsEnabled = false;
Canvas.SetTop(txt, yPos);
Canvas.SetLeft(txt, j * 30);
canvas.Children.Add(txt);
list.Add(txt);
}
_dict.Add(i, list);
yPos += 15;
}
}
}
}
What I learned, with trial and error and the feedback from user Clemens (many thanks!) is the following.
If you want to draw fast, don't use the CompositionTarget event. Draw directly instead. Needless to say, try to keep the amount of drawing needed to a minimum.
When the timing of drawing is critical (e.g. animation), I'd say, don't rely on events too much. My event invoking program wasn't too precise.
For now, WPF drawing speed is fast enough. If I run into serious problems, I might check out if MonoGame / SkiaSharp are tools that can improve performance.
What I did in order to fix my problem, was calculating the amount of elapsed ticks between each "frame" beforehand. Then, when the music starts playing, I start updating the screen in a loop. I draw a frame, and wait for the amount of ticks until the next frame. To keep everything in sync, I compare the amount of music elapsed ticks with the animation elapsed ticks and change the frame speed accordingly, if that makes any sense ;)
EDIT:
Though I got great speed improvements, it wasn't enough. I switched to MonoGame. I also learned that classic WinForms has slightly better performance than WPF.
Related
We have to show a legal pop-up in our WPF app. When the pop-up is shown, we use blur effect on the view below.
Recently we recognized that this is causing high GPU usage. Because of the spinner control in the background. The more active content, the more GPU usage.
We collapse this spinner when the pop-up is shown based on a property. But this doesn't help. Only when we set it to collapsed in MainWindow.xaml it works.
We tried multiple things e.g. BitmapCache and other techniques but with no success so far.
Here an example:
https://github.com/rmoergeli/BlurEffectTest.git
I investigated that the problem in demo code hides in your animation: it doesn't stop on changing it visibility from visible to collapsed.
So I found a solution to stop animation on being collapsed with the help of MSDN resource.
public partial class Spinner : UserControl
{
private Canvas _content;
private Storyboard _rotationStoryboard;
public Spinner()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
DefineContent();
SizeChanged += Spinner_SizeChanged;
IsVisibleChanged += Spinner_IsVisibleChanged;
Loaded += Spinner_Loaded;
}
private void Spinner_Loaded(object sender, RoutedEventArgs e)
{
_rotationStoryboard.Begin(this, isControllable: true);
}
private void Spinner_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool isVisible && isVisible)
_rotationStoryboard.Resume(this);
else
_rotationStoryboard.Pause(this);
}
private void DefineContent()
{
_content = new Canvas();
//set content render transform origin point to center
_content.RenderTransformOrigin = new Point(0.5 , 0.5);
_content.RenderTransform = new RotateTransform(angle: 0);
// Assign the canvas a name by
// registering it with the page, so that
// it can be targeted by storyboard
// animations.
RegisterName("animatableCanvas", _content);
Content = _content;
DefineAnimatableContent();
// Create an animation and a storyboard to animate the
// canvas.
DoubleAnimation doubleAnimation = new DoubleAnimation
{
To = 360,
Duration = new Duration(TimeSpan.FromSeconds(3)),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetName(doubleAnimation, "animatableCanvas");
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RenderTransform.Angle"));
_rotationStoryboard = new Storyboard();
_rotationStoryboard.Children.Add(doubleAnimation);
}
private void DefineAnimatableContent() //renamed Refresh method
{
int n = Balls;
double size = BallSize;
_content.Children.Clear();
double x = ActualWidth / 2;
double y = ActualHeight / 2;
double r = Math.Min(x, y) - size / 2;
double doubleN = Convert.ToDouble(n);
for (int i = 1; i <= n; i++)
{
double doubleI = Convert.ToDouble(i);
double x1 = x + Math.Cos(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
double y1 = y + Math.Sin(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
var e = new Ellipse
{
Fill = BallBrush,
Opacity = doubleI / doubleN,
Height = size,
Width = size
};
Canvas.SetLeft(e, x1);
Canvas.SetTop(e, y1);
_content.Children.Add(e);
};
}
#region Event Handlers
private void Spinner_SizeChanged(object sender, SizeChangedEventArgs e)
{
//we dont need this anymore as we set content render transform origin point to content center
//Transform.CenterX = ActualWidth / 2;
//Transform.CenterY = ActualHeight / 2;
DefineAnimatableContent();
}
#endregion
//other logic
}
This code stops animation on changing Spiner control visibility. But sure you can pause and resume animation with any trigger you need.
N.B! Using this approach you define all the content (not only animarable parts) in code behind so you don't need Spinner.xaml resource anymore.
I making animation.
Draw a path with use a ArcSegment.
And moves along to the path.
However, different from startingpoint of animation and startingpoint of path.
What should modify?
this image .. Immediately after the starting the animation.
Circle location is in the middle of the Path.
I Want to help me. please...
animation part. source code
i create demo version.
my source code written in korean. so create demo version
If you continue to click the second button.
Different starting point of the path and starting point Of a circle.
If there are other methods that may be.
It does not matter all when you change the source.
This is just to succeed. We look forward to good results.
You are a really good person. thank you
xaml
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="400" Width="830">
<Grid>
<Button x:Name="num_bt" Content="1.create number" HorizontalAlignment="Left" VerticalAlignment="Top" Width="118" Click="num_bt_Click"/>
<Button x:Name="start" Content="2.start animation" HorizontalAlignment="Left" Margin="-2,50,0,0" VerticalAlignment="Top" Width="118" Click="draw_bt_Click"/>
<Label Content="contiue click" HorizontalAlignment="Left" Margin="0,97,0,0" VerticalAlignment="Top" Width="116"/>
<Label Content="start animation btn" HorizontalAlignment="Left" Margin="0,120,0,0" VerticalAlignment="Top" Width="116"/>
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="373" Margin="137,0,0,0" VerticalAlignment="Top" Width="685"/>
</Grid>
code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication7
{
/// <summary>
/// MainWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class MainWindow : Window
{
int num_count = 10; // use data number
List<int> sampleNumbers; // Save a number to use for sorting
int count = 1; // count
Ellipse[] Circle;
List<double> SaveCircleStartPoint;
struct SaveCircleProperty
{
public double Circle_stposX;
public double Circle_stposY;
public double radian;
}
List<SaveCircleProperty> SaveCircleInfos;
// save changed sorting info
struct SortingTraceInfo
{
public int Position; // change position
public int TargetPosition; // will change position
public int[] SortingNumbers; // sorting numbers
}
// save changed sorting info
List<SortingTraceInfo> sortingInfos;
int sortingInfosIndex;
public MainWindow()
{
InitializeComponent();
}
private void num_bt_Click(object sender, RoutedEventArgs e)
{
Random _random = new Random(); // create number
int[] num = new int[10];
sampleNumbers = new List<int>();
for (int i = 0; i < num_count; i++)
{
num[i] = _random.Next(1, 50);
sampleNumbers.Add(num[i]); // save number data
}
BubbleSort();
drawCircle(num_count, sampleNumbers);
}
private void draw_bt_Click(object sender, RoutedEventArgs e)
{
// draw a path and start animation
SortingTraceInfo traceInfo = this.sortingInfos[this.sortingInfosIndex++];
draw_path(traceInfo.Position, traceInfo.TargetPosition);
}
// draw circle
private void drawCircle(int num, List<int> size)
{
// saving a position to draw a circle
SaveCircleStartPoint = new List<double>();
SaveCircleInfos = new List<SaveCircleProperty>();
// To create the circle by the number of input num
Circle = new Ellipse[num];
int location = ((685 / num) - 5); // circle startpoint
int x = 50; // Width
int y = 115; // Height
for (int i = 0; i < num; i++)
{
double circlesize = size[i] + 10;
double radius = circlesize / 2; // radius
double st_posX = x - radius; // circular X-axis position
double st_posY = y - radius; // circular Y-axis position
SaveCircleProperty cp = new SaveCircleProperty();
// define circle property
Circle[i] = new Ellipse();
Circle[i].Name = "circle" + i.ToString();
Circle[i].Stroke = Brushes.Red;
Circle[i].StrokeThickness = 5;
Circle[i].Width = circlesize;
Circle[i].Height = circlesize;
// position of canvs
Canvas.SetLeft(Circle[i], st_posX); // startpoint x-axis
Canvas.SetTop(Circle[i], st_posY); // startpoint Y-axis
// save circls's property
SaveCircleStartPoint.Add(st_posX);
cp.Circle_stposX = st_posX;
cp.Circle_stposY = st_posY;
cp.radian = radius;
SaveCircleInfos.Add(cp);
// startpoint
x += location;
// add canvas
canvas.Children.Add(Circle[i]);
this.RegisterName(Circle[i].Name, Circle[i]);
}
}
private void draw_path(int pos1, int pos2)
{
SaveCircleProperty scp = this.SaveCircleInfos[pos1];
SaveCircleProperty scp2 = this.SaveCircleInfos[pos2];
create_path(scp.Circle_stposX, scp.Circle_stposY, scp2.Circle_stposX, scp2.Circle_stposY, scp.radian, scp2.radian, pos1, pos2);
}
private void create_path(double startPoint, double startPoint2, double endPoint, double endPoint2, double radian1, double radian2, int position, int tartgetPosition)
{
// regist name
NameScope.SetNameScope(this, new NameScope());
NameScope.SetNameScope(canvas, new NameScope());
// circls position reset
Canvas.SetLeft(Circle[position], 0);
Canvas.SetTop(Circle[position], 0);
Canvas.SetLeft(Circle[tartgetPosition], 0);
Canvas.SetTop(Circle[tartgetPosition], 0);
///<summary>
/// Circle left the road going from left to right
///</summary>
PathGeometry pathGeometryLeft = new PathGeometry();
PathFigure Leftfigure = new PathFigure();
Leftfigure.StartPoint = new Point(startPoint, startPoint2); // x-axis , y-axis start point
// point(x-axis, y-axis)
Leftfigure.Segments.Add(new ArcSegment(new Point(endPoint, startPoint2), new Size(150, endPoint - startPoint), 90, false, SweepDirection.Clockwise, true));
pathGeometryLeft.Figures.Add(Leftfigure);
Path Leftpath = new Path();
Leftpath.Data = pathGeometryLeft;
Leftpath.Stroke = Brushes.Green;
canvas.Children.Add(Leftpath);
MatrixTransform LeftcircleMatrixTransform = new MatrixTransform();
Circle[position].RenderTransform = LeftcircleMatrixTransform;
this.RegisterName("LeftCircleMatrixTransform" + count.ToString(), LeftcircleMatrixTransform);
pathGeometryLeft.Freeze();
// Create a MatrixAnimationUsingPath to move the
// circle along the path by animating
// its MatrixTransform.
MatrixAnimationUsingPath Leftmatrixanimation = new MatrixAnimationUsingPath();
Leftmatrixanimation.PathGeometry = pathGeometryLeft;
Leftmatrixanimation.Duration = TimeSpan.FromSeconds(5);
Leftmatrixanimation.DoesRotateWithTangent = true;
// Set the animation to target the Matrix property
// of the MatrixTransform named "ButtonMatrixTransform".
Storyboard.SetTargetName(Leftmatrixanimation, "LeftCircleMatrixTransform" + count.ToString());
Storyboard.SetTargetProperty(Leftmatrixanimation, new PropertyPath(MatrixTransform.MatrixProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard LeftpathAnimationStoryboard = new Storyboard();
LeftpathAnimationStoryboard.Children.Add(Leftmatrixanimation);
LeftpathAnimationStoryboard.Begin(this);
/// <summary>
/// The road to the right circle from right to left (path)
/// </summary>
PathGeometry RightpathGeometry = new PathGeometry();
PathFigure Rightfigure = new PathFigure();
Rightfigure.StartPoint = new Point(endPoint, endPoint2 + (radian2 * 2)); // x축 , y축 시작점
// point(x축 끝, y축 끝점)
Rightfigure.Segments.Add(new ArcSegment(new Point(startPoint, endPoint2 + (radian2 * 2)), new Size(150, endPoint - startPoint), 90, false, SweepDirection.Clockwise, true));
// this.RegisterName("RightmyArcSegment", Rightfigure.Segments);
RightpathGeometry.Figures.Add(Rightfigure);
Path Rightpath = new Path();
Rightpath.Data = RightpathGeometry;
Rightpath.Stroke = Brushes.Red;
canvas.Children.Add(Rightpath);
MatrixTransform RightcircleMatrixTransform = new MatrixTransform();
Circle[tartgetPosition].RenderTransform = RightcircleMatrixTransform;
this.RegisterName("RightCircleMatrixTransform" + count.ToString(), RightcircleMatrixTransform);
RightpathGeometry.Freeze();
MatrixAnimationUsingPath Rightmatrixanimation = new MatrixAnimationUsingPath();
Rightmatrixanimation.PathGeometry = RightpathGeometry;
Rightmatrixanimation.Duration = TimeSpan.FromSeconds(10);
// Set the animation's DoesRotateWithTangent property
// to true so that rotates the rectangle in addition
// to moving it.
Rightmatrixanimation.DoesRotateWithTangent = true;
// Set the animation to target the Matrix property
// of the MatrixTransform named "CircleMatrixTransform".
Storyboard.SetTargetName(Rightmatrixanimation, "RightCircleMatrixTransform" + count.ToString());
Storyboard.SetTargetProperty(Rightmatrixanimation, new PropertyPath(MatrixTransform.MatrixProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard RightpathAnimationStoryboard = new Storyboard();
RightpathAnimationStoryboard.Children.Add(Rightmatrixanimation);
RightpathAnimationStoryboard.Begin(this);
Ellipse temp = null;
temp = Circle[position];
Circle[position] = Circle[tartgetPosition];
Circle[tartgetPosition] = temp;
count++;
}
// 버블 정렬
private List<int> BubbleSort()
{
sortingInfos = new List<SortingTraceInfo>();
List<int> sorting = new List<int>(sampleNumbers);
for (int i = 0; i < sorting.Count - 1; i++)
{
for (int j = 0; j < sorting.Count - 1 - i; j++)
{
if (sorting[j] > sorting[j + 1])
{
Swap(sorting, j, j + 1);
SortingTraceInfo sortInfo = new SortingTraceInfo(); //
sortInfo.Position = j; // save change position
sortInfo.TargetPosition = j + 1; // save will change position
sortInfo.SortingNumbers = sorting.ToArray(); // sorting number saved to array
sortingInfos.Add(sortInfo); // 변경 정보등을 sortingInfos 리스트에 저장
}
}
}
return sorting;
}
private void Swap(List<int> num, int i, int j)
{
int temp = num[i];
num[i] = num[j];
num[j] = temp;
}
}
}
private void draw_path(int pos1, int pos2)
{
var circles = canvas.Children.OfType<Ellipse>().OrderBy(q => (double)q.GetValue(Canvas.LeftProperty)).ToList();
var circle1 = circles[pos1];
var circle2 = circles[pos2];
// horizontal animation for circle1
Storyboard sb1 = new Storyboard();
double from1 = (double)circle1.GetValue(Canvas.LeftProperty);
double to1 = (double)circle2.GetValue(Canvas.LeftProperty) + circle2.ActualWidth / 2 - circle1.ActualWidth / 2;
DoubleAnimation da1 = new DoubleAnimation(from1, to1, new Duration(TimeSpan.FromSeconds(0.6)));
Storyboard.SetTarget(sb1, circle1);
Storyboard.SetTargetProperty(sb1, new PropertyPath(Canvas.LeftProperty));
sb1.Children.Add(da1);
// horizontal animation for circle2
Storyboard sb2 = new Storyboard();
double from2 = (double)circle2.GetValue(Canvas.LeftProperty);
double to2 = (double)circle1.GetValue(Canvas.LeftProperty) + circle1.ActualWidth / 2 - circle2.ActualWidth / 2;
DoubleAnimation da2 = new DoubleAnimation(from2, to2, new Duration(TimeSpan.FromSeconds(0.6)));
Storyboard.SetTarget(sb2, circle2);
Storyboard.SetTargetProperty(sb2, new PropertyPath(Canvas.LeftProperty));
sb2.Children.Add(da2);
// vertical animation for circle1
Storyboard sb3 = new Storyboard();
double from3 = (double)circle1.GetValue(Canvas.TopProperty);
double to3 = (double)circle1.GetValue(Canvas.TopProperty) + circle1.ActualWidth;
DoubleAnimation da3 = new DoubleAnimation(from3, to3, new Duration(TimeSpan.FromSeconds(0.3)));
da3.AutoReverse = true;
da3.AccelerationRatio = 0.1;
Storyboard.SetTarget(sb3, circle1);
Storyboard.SetTargetProperty(sb3, new PropertyPath(Canvas.TopProperty));
sb3.Children.Add(da3);
// vertical animation for circle2
Storyboard sb4 = new Storyboard();
double from4 = (double)circle2.GetValue(Canvas.TopProperty);
double to4 = (double)circle2.GetValue(Canvas.TopProperty) - circle2.ActualWidth;
DoubleAnimation da4 = new DoubleAnimation(from4, to4, new Duration(TimeSpan.FromSeconds(0.3)));
da4.AutoReverse = true;
da4.AccelerationRatio = 0.1;
Storyboard.SetTarget(sb4, circle2);
Storyboard.SetTargetProperty(sb4, new PropertyPath(Canvas.TopProperty));
sb4.Children.Add(da4);
sb1.Begin();
sb2.Begin();
sb3.Begin();
sb4.Begin();
}
Hope, it helps
I am working on a "bottom-right docked" popup form.
The layout of the form follows this structure:
Header
Content (text message)
Footer
This form should resize according to its content.
I am using SetBounds to make it grow to the top instead of the bottom (remember that the window is docked bottom-right).
However, when the animation occurs, the footer redraws itself in a very bad way, as its form-relative location is continuously updated.
I provide a sample to give you an idea:
using System.Windows.Forms;
namespace AnimatorTest
{
public class Form3 : Form
{
Timer timer = new Timer();
public Form3()
{
timer.Interval = 30;
timer.Tick += timer_Tick;
// Create 3 test buttons
for (int i = 0; i < 3; i++)
{
Button b = new Button() { Dock = DockStyle.Bottom };
b.Click += (s, e) => timer.Start();
b.Text = "Click and watch how ugly I am during the animation.";
Controls.Add(b);
}
Height = 100;
StartPosition = FormStartPosition.CenterScreen;
}
void timer_Tick(object sender, System.EventArgs e)
{
int desiredHeight = 500;
int difference = desiredHeight - Height;
int change = difference / 6;
if (System.Math.Abs(change) < 1)
{
SetBounds(Left, Top - difference, Width, Height + difference);
timer.Stop();
}
else
{
SetBounds(Left, Top - change, Width, Height + change);
}
}
}
}
I don't really have any idea to work around this.
Thanks.
(x,y) =(10,20),(50,30),(20,20),(40,22),(45,20),(50,35),.........
I want to create an animation with .Net.Normally I am using windows form. But if needed, i can use WPF.
It starts with (10,20) point.Actually a line starts from (10,20) then goes to (50,30) point with 10ms delay. then, from (50,30) to (20,20) after 10ms and so on.
This values will be read from a text file. I can simply make two ArrayList x&y, to put values from the text file.I just Have to know how could i generate the animated line from this x,y with 10 ms delay from each node to another?
If my question is difficult to understand, please let me know. I will try to make it easier.
Thanks in Advance.
If I understand you correctly, you want to animate the line as it would be just drawn. Here is a simple example with your values.
<Window x:Class="WpfApplication1.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">
<Canvas Name="lineCanvas" MouseLeftButtonDown="lineCanvas_MouseLeftButtonDown" Background="White" />
</Window>
The event handler will start the animation later. First, we'll define the data:
List<Point> Points = new List<Point>();
Storyboard sb;
public MainWindow()
{
InitializeComponent();
Points.Add(new Point(10, 20));
Points.Add(new Point(50, 30));
Points.Add(new Point(20, 20));
Points.Add(new Point(40, 22));
Points.Add(new Point(45, 20));
Points.Add(new Point(50, 35));
InitAnimation();
}
sb is the Storyboard that will contain the animation. Points can easily be filled with the values from a file.
Let's initialize the animation. A new line will be added for each segment. Then the endpoint of the line will be animated.
public void InitAnimation()
{
sb = new Storyboard();
for (int i = 0; i < Points.Count - 1; ++i)
{
//new line for current line segment
var l = new Line();
l.Stroke = Brushes.Black;
l.StrokeThickness = 2;
//data from list
var startPoint = Points[i];
var endPoint = Points[i + 1];
//set startpoint = endpoint will result in the line not being drawn
l.X1 = startPoint.X;
l.Y1 = startPoint.Y;
l.X2 = startPoint.X;
l.Y2 = startPoint.Y;
lineCanvas.Children.Add(l);
//Initialize the animations with duration of 1 second for each segment
var daX = new DoubleAnimation(endPoint.X, new Duration(TimeSpan.FromMilliseconds(1000)));
var daY = new DoubleAnimation(endPoint.Y, new Duration(TimeSpan.FromMilliseconds(1000)));
//Define the begin time. This is sum of durations of earlier animations + 10 ms delay for each
daX.BeginTime = TimeSpan.FromMilliseconds(i * 1010);
daY.BeginTime = TimeSpan.FromMilliseconds(i * 1010);
sb.Children.Add(daX);
sb.Children.Add(daY);
//Set the targets for the animations
Storyboard.SetTarget(daX, l);
Storyboard.SetTarget(daY, l);
Storyboard.SetTargetProperty(daX, new PropertyPath(Line.X2Property));
Storyboard.SetTargetProperty(daY, new PropertyPath(Line.Y2Property));
}
}
The duration of the animations can easily be changed according to the length of the line to make it look nicer.
Last task, show the animation:
private void lineCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
sb.Begin(this);
}
Here's a variation on Nico's great answer. I use PointAnimation to animate the EndPoint of a LineGeometry which serves as the Data for a Path.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SoGeneratingAnimatedLine
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var canvas = new Canvas();
Content = canvas;
var points =
new List<Point>()
{
new Point(10, 10),
new Point(90, 10),
new Point(90, 90),
new Point(10, 90),
new Point(10, 10)
};
var sb = new Storyboard();
for (int i = 0; i < points.Count - 1; i++)
{
var lineGeometry = new LineGeometry(points[i], points[i]);
var path =
new Path()
{
Stroke = Brushes.Black,
StrokeThickness = 2,
Data = lineGeometry
};
canvas.Children.Add(path);
var animation =
new PointAnimation(points[i], points[i + 1], new Duration(TimeSpan.FromMilliseconds(1000)))
{
BeginTime = TimeSpan.FromMilliseconds(i * 1010)
};
sb.Children.Add(animation);
RegisterName("geometry" + i, lineGeometry);
Storyboard.SetTargetName(animation, "geometry" + i);
Storyboard.SetTargetProperty(animation, new PropertyPath(LineGeometry.EndPointProperty));
}
MouseDown += (s, e) => sb.Begin(this);
}
}
}
I'm trying to use a ListView component to show around 1,000 image thumbnails and I'm having some performance problems.
First I create an ImageList containing my 1,000 images. This is lightning fast and takes under a second.
However, once I assign the ImageList to my ListView, it takes around 10+ seconds.
Example:
ImageList _imgList = GetMyImageList(); // takes under 1 second
ListView _lstView = new ListView();
lstView.LargeImageList = _imgList; // takes 10+ seconds
Is there anything I can do to increase performance? My ImageList contains images that are already resized into thumbnail size (197x256 pixels) so that's not the problem... (and creating my ImageList only takes 1 second at the most).
Does the data in your list view change frequently? Do you load new image lists frequently?
I tried your scenario and got a few seconds of loading time (since I'm generating random images) but very fast refresh times when changing list view [View] modes as well as scrolling.
Here is the sample code. Try it out and let me know how it works.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace WindowsFormsApplication10
{
public partial class FormListView:
System.Windows.Forms.Form
{
public FormListView ()
{
string [] names = null;
this.InitializeComponent();
names = Enum.GetNames(typeof(View));
for (int i=0; i < names.Length; i++)
{
this.comboBox1.Items.Add(names [i]);
if (names [i] == this.ListView.View.ToString())
this.comboBox1.SelectedIndex = i;
}
}
private void comboBox1_SelectedIndexChanged (object sender, EventArgs e)
{
this.ListView.View = (View) Enum.Parse(typeof(View), this.comboBox1.SelectedItem.ToString());
this.ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
}
private void ButtonLoadImages_Click (object sender, System.EventArgs e)
{
Image image;
Stopwatch watch;
this.Enabled = false;
this.Cursor = Cursors.WaitCursor;
this.ListView.SmallImageList = null;
this.ListView.LargeImageList = null;
this.ListView.StateImageList = null;
while (this.ImageList.Images.Count > 0)
{
this.ImageList.Images [0].Dispose();
this.ImageList.Images.RemoveAt(0);
}
this.ImageList.ImageSize = new System.Drawing.Size(256, 256);
watch = Stopwatch.StartNew();
for (int i=0; i < 1000; i++)
{
image = new Bitmap(this.ImageList.ImageSize.Width, this.ImageList.ImageSize.Height);
using (Graphics graphics = Graphics.FromImage(image))
{
graphics.Clear(Color.White);
graphics.DrawRectangle(Pens.Red, 10, 10, this.ImageList.ImageSize.Width - 20, this.ImageList.ImageSize.Height - 20);
graphics.DrawString(i.ToString(), this.Font, Brushes.Blue, 20, 20);
}
this.ImageList.Images.Add(image);
}
watch.Stop();
this.ListView.SmallImageList = this.ImageList;
this.ListView.LargeImageList = this.ImageList;
this.ListView.StateImageList = this.ImageList;
this.Text = watch.Elapsed.TotalSeconds.ToString();
this.Cursor = Cursors.Default;
this.Enabled = true;
}
private void ButtonLoadItems_Click (object sender, System.EventArgs e)
{
Stopwatch watch;
ListViewItem item;
this.Enabled = false;
this.Cursor = Cursors.WaitCursor;
this.ListView.Items.Clear();
this.ListView.Columns.Clear();
this.ListView.Columns.Add("Id", "Id");
this.ListView.Columns.Add("Name", "Name");
this.ListView.SmallImageList = null;
this.ListView.LargeImageList = null;
this.ListView.StateImageList = null;
this.ListView.BeginUpdate();
watch = Stopwatch.StartNew();
for (int i=0; i < 1000; i++)
{
item = new ListViewItem();
item.ImageIndex = i;
item.Text = i.ToString();
item.SubItems.Add("qwerty");
this.ListView.Items.Add(item);
}
this.ListView.EndUpdate();
this.ListView.SmallImageList = this.ImageList;
this.ListView.LargeImageList = this.ImageList;
this.ListView.StateImageList = this.ImageList;
this.ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
watch.Stop();
this.Text = watch.Elapsed.TotalSeconds.ToString();
this.Cursor = Cursors.Default;
this.Enabled = true;
}
}
}