|
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Media.Effects;
using System;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Reflection;
namespace WpfApplication1
{
public enum DrawingMode
{
ImageOnly,
AnimateImage,
AnimateImageAndCircles
}
public class GraphContext : Canvas
{
private delegate void AnimationDelegate();
#region Declarations
private List<Points> _points;
private Spirograph _graph;
private Point _firstPoint;
private bool _changed;
private int _animations;
private System.Timers.Timer _timer;
private DrawingContext _context;
private Ellipse _fixedCircle;
private Ellipse _movingCircle;
private Ellipse _pen;
private Line _spoke;
#endregion
#region Methods
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
AnimationDelegate animationMethod = new AnimationDelegate(this.AnimateGraphThreaded);
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, animationMethod);
if (_animations == _points.Count - 1)
{
_timer.Stop();
AnimationDelegate gapMethod = new AnimationDelegate(this.AnimateGraphGapThreaded);
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, gapMethod);
}
}
catch(TargetInvocationException){}
}
/// <summary>
/// Sets the epicycloid so plotting points can be retrieved.
/// </summary>
/// <param name="graph">The graph.</param>
internal void SetGraph(Spirograph graph)
{
if (_timer != null)
{
_timer.Stop();
}
// Clear residual animation objects
this.Children.Remove(_fixedCircle);
this.Children.Remove(_movingCircle);
this.Children.Remove(_pen);
this.Children.Remove(_spoke);
_fixedCircle = null;
_movingCircle = null;
_pen = null;
_spoke = null;
_changed = true;
_graph = graph;
_points = _graph.GetPoints();
// Remove the first point so no line is drawn from 0,0 to the first plot point
_firstPoint = _points[0].ToPoint;
_points.RemoveAt(0);
this.InvalidateVisual();
}
/// <summary>
/// Graph drawing implementation
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
private void Draw(DrawingContext drawingContext)
{
// Remove any current graph
this.Children.Clear();
// Plot the points
if (_points != null && _points.Count > 0)
{
switch(this.DrawingMode)
{
case DrawingMode.ImageOnly:
DrawStaticGraph(drawingContext);
break;
case DrawingMode.AnimateImage:
case DrawingMode.AnimateImageAndCircles:
AnimateGraph(drawingContext);
break;
}
}
}
/// <summary>
/// Draws a static image of the graph.
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
private void DrawStaticGraph(DrawingContext drawingContext)
{
// PathGeometry is a nice alternative to drawingContext.DrawLine(...) as it
// allows the points to be rendered as an image that can be further manipulated
PathGeometry geometry = new PathGeometry();
// Add all points to the geometry
foreach (Points pointXY in _points)
{
PathFigure figure = new PathFigure();
figure.StartPoint = pointXY.FromPoint;
figure.Segments.Add(new LineSegment(pointXY.ToPoint, true));
geometry.Figures.Add(figure);
}
// Add the first point to close the gap from the graph's end point to graph's start point
PathFigure lastFigure = new PathFigure();
lastFigure.StartPoint = _points[_points.Count - 1].FromPoint;
lastFigure.Segments.Add(new LineSegment(_firstPoint, true));
geometry.Figures.Add(lastFigure);
// Create a new drawing and drawing group in order to apply a custom drawing effect
GeometryDrawing drawing = new GeometryDrawing(this.Pen.Brush, this.Pen, geometry);
DrawingGroup drawingGroup = new DrawingGroup();
drawingGroup.Children.Add(drawing);
// Clip the drawing so it doesn't extend outside the canvas boundaries
drawingGroup.ClipGeometry = new RectangleGeometry(new Rect(0, 0, this.ActualWidth, this.ActualHeight));
// Create a blur effect (i.e. "softness")
BlurBitmapEffect blurEffect = new BlurBitmapEffect();
blurEffect.Radius = Softness;
drawingGroup.BitmapEffect = blurEffect;
// Use an Image control and a DrawingImage to display the graph
DrawingImage drawingImage = new DrawingImage(drawingGroup);
// Freeze the DrawingImage for performance benefits.
drawingImage.Freeze();
Image image = new Image();
image.Source = drawingImage;
image.Stretch = Stretch.None;
// Center the image on the canvas
double x = this.ActualWidth / 2 - drawingImage.Width / 2;
double y = this.ActualHeight / 2 - drawingImage.Height / 2;
image.Margin = new Thickness(x, y, 1, 1);
// Add the image to the UI
this.Children.Add(image);
}
/// <summary>
/// Inserts the animated point.
/// </summary>
/// <param name="fromX">From X.</param>
/// <param name="fromY">From Y.</param>
/// <param name="toX">To X.</param>
/// <param name="toY">To Y.</param>
private void InsertAnimatedPoint(Points pointXY)
{
// Create a line segment from the to and from points
Line segmentAnimation = new Line();
segmentAnimation.X1 = pointXY.FromPoint.X;
segmentAnimation.Y1 = pointXY.FromPoint.Y;
segmentAnimation.X2 = pointXY.ToPoint.X;
segmentAnimation.Y2 = pointXY.ToPoint.Y;
// Create a blur effect (i.e. "softness")
BlurBitmapEffect blurEffect = new BlurBitmapEffect();
blurEffect.Radius = Softness;
// Apply the blur and draw the line
segmentAnimation.Stroke = this.Pen.Brush;
segmentAnimation.StrokeThickness = this.Pen.Thickness;
segmentAnimation.BitmapEffect = blurEffect;
this.Children.Insert(0, segmentAnimation);
// Draw fixed and moving circle elements if animation is selected
if (DrawingMode == DrawingMode.AnimateImageAndCircles && _context != null)
{
DrawFixedCircle();
DrawMovingCircle(pointXY);
DrawPen(pointXY);
DrawSpoke(pointXY);
}
}
/// <summary>
/// Draws the fixed circle while animating.
/// </summary>
private void DrawFixedCircle()
{
// Only draw the fixed circle one time
if (_fixedCircle == null)
{
// Create the circle
_fixedCircle = new Ellipse();
_fixedCircle.Width = _graph.InnerRadius * 2;
_fixedCircle.Height = _graph.InnerRadius * 2;
_fixedCircle.Stroke = new SolidColorBrush(Colors.White);
_fixedCircle.StrokeThickness = 1;
// Get the center x and y coordinates
double x = this.ActualWidth / 2 - _graph.InnerRadius;
double y = this.ActualHeight / 2 - _graph.InnerRadius;
_fixedCircle.Margin = new Thickness(x, y, 1, 1);
// Add the circle to the canvas
this.Children.Add(_fixedCircle);
}
}
/// <summary>
/// Draws the moving circle while animating.
/// </summary>
/// <param name="pointXY">The point XY.</param>
private void DrawMovingCircle(Points pointXY)
{
// Remove the moving circle from the UI element list since it mves each
// time the graph is painted
if (_movingCircle != null)
{
this.Children.Remove(_movingCircle);
}
// Create the circle
_movingCircle = new Ellipse();
_movingCircle.Width = Math.Abs(_graph.OuterRadius * 2);
_movingCircle.Height = Math.Abs(_graph.OuterRadius * 2);
_movingCircle.Stroke = new SolidColorBrush(Colors.White);
_movingCircle.StrokeThickness = 1;
// Get the center x and y coordinates
double x = pointXY.CenterX - pointXY.Sign * _graph.OuterRadius;
double y = pointXY.CenterY - pointXY.Sign * _graph.OuterRadius;
_movingCircle.Margin = new Thickness(x, y, 1, 1);
// Add the circle to the canvas
this.Children.Add(_movingCircle);
}
/// <summary>
/// Draws the pen point while animating.
/// </summary>
/// <param name="pointXY">The point XY.</param>
private void DrawPen(Points pointXY)
{
// Remove the pene from the UI element list since it mves each
// time the graph is painted
if (_pen != null)
{
this.Children.Remove(_pen);
}
_pen = new Ellipse();
_pen.Width = 5;
_pen.Height = 5;
_pen.Fill = new SolidColorBrush(Colors.White);
_pen.Margin = new Thickness(pointXY.ToPoint.X - 2.5, pointXY.ToPoint.Y - 2.5, 1, 1);
this.Children.Add(_pen);
}
/// <summary>
/// Draws the spoke while animating.
/// </summary>
/// <param name="pointXY">The point XY.</param>
private void DrawSpoke(Points pointXY)
{
// Remove the spoke from the UI element list since it mves each
// time the graph is painted
if (_spoke != null)
{
this.Children.Remove(_spoke);
}
double direction = pointXY.Sign;
// Determine if the spoke is inside our outside the moving circle
if (pointXY.Position < 0)
{
direction *= -1;
}
// Get the X and Y coordinates of the circle's edge
double x = pointXY.CenterX + direction * _graph.OuterRadius * Math.Cos(pointXY.Phi);
double y = pointXY.CenterY - direction * _graph.OuterRadius * Math.Sin(pointXY.Phi);
_spoke = new Line();
_spoke.Name = "spoke";
_spoke.X1 = x;
_spoke.Y1 = y;
_spoke.X2 = pointXY.ToPoint.X;
_spoke.Y2 = pointXY.ToPoint.Y;
// Apply the blur and draw the line
_spoke.Stroke = new SolidColorBrush(Colors.White);
_spoke.StrokeThickness = 1;
this.Children.Insert(0, _spoke);
}
/// <summary>
/// Animates the graph.
/// </summary>
/// <param name="drawingContext">The drawing context.</param>
private void AnimateGraph(DrawingContext drawingContext)
{
// Start the timer to draw a new image every X seconds
_context = drawingContext;
_animations = 0;
_timer = new System.Timers.Timer();
_timer.Interval = Velocity;
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
_timer.Start();
}
/// <summary>
/// Threaded method for graph animation
/// </summary>
private void AnimateGraphThreaded()
{
try
{
InsertAnimatedPoint(_points[_animations++]);
}
catch (IndexOutOfRangeException) { }
}
/// <summary>
/// Threaded method for graph animation completion (i.e. connect the start and end points)
/// </summary>
private void AnimateGraphGapThreaded()
{
try
{
Points points = new Points();
points.FromPoint = _points[_points.Count - 1].FromPoint;
points.ToPoint = _firstPoint;
points.CenterX = _points[0].CenterX;
points.CenterY = _points[0].CenterY;
points.Phi = _points[0].Phi;
points.Position = _points[0].Position;
points.Sign = _points[0].Sign;
InsertAnimatedPoint(points);
}
catch (IndexOutOfRangeException) { }
}
#endregion
#region Overridden Methods
/// <summary>
/// Draws the graph
/// </summary>
/// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (_changed)
{
_changed = false;
Draw(drawingContext);
}
}
/// <summary>
/// Redraws the graph since the display area's height and width changed
/// </summary>
/// <param name="sizeInfo">Details of the old and new size involved in the change.</param>
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
_changed = true;
this.InvalidateVisual();
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the softness of the graph's line.
/// </summary>
/// <value>The drawing line softness.</value>
public double Softness { get; set; }
/// <summary>
/// Gets or sets the pen.
/// </summary>
/// <value>The pen used for drawing.</value>
public Pen Pen { get; set; }
/// <summary>
/// Gets or sets the pen's brush.
/// </summary>
/// <value>The pen's brush.</value>
public Brush Brush { get; set; }
/// <summary>
/// Gets or sets the drawing mode.
/// </summary>
/// <value>The drawing mode.</value>
public DrawingMode DrawingMode { get; set; }
/// <summary>
/// Gets or sets the velocity of the animation.
/// </summary>
/// <value>The velocity.</value>
public double Velocity { get; set; }
#endregion
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
I'm the founder of PageLabs, a web-based performance and SEO optimization site.
Give your site a boost in performance, even take a free speed test!
http://www.pagelabs.com