I have the basic idea of how it can be done but I don't really know the code. I want to use a WPF application in Visual Studio. When the user clicks a "Draw" button, it would draw a shape (a spirograph), on the canvas (using a polyline) but the twist is, it needs to draw it point by point, one line at a time so you will see this "animation." Also, the user should be able to cancel/stop the drawing while it is being drawn on canvas. First, it will need to generate a list or array of points (I have more familiarity with arrays), then pass the points to a background worker which will "report its progress" by drawing the shape slowly on the canvas. Here is the code for drawing a spirograph but any shape is ok really.
public void DrawSpiroGraph()
{
for (inti = 0; i<= numPoints; i++)
{
pt = newPoint();
pt.X = x0 + r * Math.Cos(a);
pt.Y = y0 + r * Math.Sin(a);
double rr = 0.5 * r;
double aa = -0.8 * a;
Point pnt = newPoint();
pnt.X = pt.X + rr * Math.Cos(aa);
pnt.Y = pt.Y + rr * Math.Sin(aa);
a += 0.5;
pline.Points.Add(pnt);
}
}
First, set up your canvas:
<Canvas Name="Canvas" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" MouseRightButtonUp="Canvas_MouseRightButtonUp">
<!-- you can customize your polyline thickness/color/etc here -->
<Polyline x:Name="Poly" Stroke="Black" StrokeThickness="1" />
</Canvas>
Then you'll need to multi-thread your application. Multi-threading in WPF is a dicey business because you can't access any of the drawing contexts from a different thread. Fortunately, the BackgroundWorker
class can save you some headaches here, as its ProgressChanged
event runs on the same thread. So, when the user clicks on the canvas:
private BackgroundWorker _animationWorker;
private void Canvas_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
var p = e.GetPosition( Canvas );
Poly.Points.Add( p );
_animationWorker = new BackgroundWorker {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true};
_animationWorker.ProgressChanged += AnimationWorkerOnProgressChanged;
_animationWorker.DoWork += AnimationWorkerOnDoWork;
_animationWorker.RunWorkerAsync( p );
}
Now that we've set up the background worker, we do most of the heavy lifting inside the DoWork
delegate:
private void AnimationWorkerOnDoWork( object sender, DoWorkEventArgs doWorkEventArgs ) {
var p = (Point) doWorkEventArgs.Argument;
const int numPoints = 1000;
var r = 100;
var a = 0.0;
var pc = new PointCollection();
for( var i = 0; i <= numPoints; i++ ) {
var pt = new Point();
pt.X = p.X + r * Math.Cos( a );
pt.Y = p.Y + r * Math.Sin( a );
double rr = 0.5 * r;
double aa = -0.8 * a;
Point pnt = new Point();
pnt.X = pt.X + rr * Math.Cos( aa );
pnt.Y = pt.Y + rr * Math.Sin( aa );
a += 0.5;
_animationWorker.ReportProgress( 0, pnt );
Thread.Sleep( 10 );
if( _animationWorker.CancellationPending ) break;
}
}
Note how we use the ReportProgress
method to pass the point out; this will enable us to access the executing thread and add to our polyline:
private void AnimationWorkerOnProgressChanged( object sender, ProgressChangedEventArgs progressChangedEventArgs ) {
var p = (Point) progressChangedEventArgs.UserState;
Poly.Points.Add( p );
}
Now the only thing that remains is to support stopping the animation. I chose to implement this on a right-click (left-click to draw, right-click to stop/clear). You can, of course, attach whatever control you wish to this functionality. Here's the right mouse button handler:
private void Canvas_MouseRightButtonUp( object sender, MouseButtonEventArgs e ) {
if( _animationWorker != null ) _animationWorker.CancelAsync();
Poly.Points.Clear(); // you may wish to do this elsewhere so the partial animation stays on the screen
}
I was a little bored today, so I whipped up a simple user control for you. Just uses an timer to animate it. The Delay/Radius/Point count dependency properties, so you can bind to them with stuff (like sliders or whatever).
User Control Xaml
<UserControl x:Class="WpfApplication1.Spirograph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:WpfApplication1">
<Canvas>
<Path Stroke="{Binding Stroke, RelativeSource={RelativeSource AncestorType=app:Spirograph}}" StrokeThickness="{Binding StrokeThickness, RelativeSource={RelativeSource AncestorType=app:Spirograph}}">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure x:Name="_figure" StartPoint="{Binding StartPoint, RelativeSource={RelativeSource AncestorType=app:Spirograph}}" />
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
User Control Code
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace WpfApplication1
{
public partial class Spirograph : UserControl
{
private DispatcherTimer _timer;
private int _pointIndex = 0;
private List<Point> _points;
private bool _loaded = false;
public Spirograph()
{
_points = new List<Point>();
CalculatePoints();
InitializeComponent();
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(Delay);
_timer.Tick += TimerTick;
this.Loaded += SpirographLoaded;
this.SizeChanged += SpirographSizeChanged;
}
void SpirographSizeChanged(object sender, SizeChangedEventArgs e)
{
bool running = Running;
Reset();
StartPoint = new Point((this.ActualWidth / 2) - Radius, (this.ActualHeight / 2) - Radius);
if (running)
Start();
}
void SpirographLoaded(object sender, RoutedEventArgs e)
{
_loaded = true;
if (AutoStart)
Start();
}
void TimerTick(object sender, EventArgs e)
{
if (_pointIndex >= PointCount)
Stop();
else
_figure.Segments.Add(new LineSegment(_points[_pointIndex], true));
_pointIndex++;
}
public bool Running { get; protected set; }
public bool AutoStart
{
get { return (bool)GetValue(AutoStartProperty); }
set { SetValue(AutoStartProperty, value); }
}
public static readonly DependencyProperty AutoStartProperty = DependencyProperty.Register("AutoStart", typeof(bool), typeof(Spirograph), new UIPropertyMetadata(true));
public int PointCount
{
get { return (int)GetValue(PointCountProperty); }
set { SetValue(PointCountProperty, value); }
}
public static readonly DependencyProperty PointCountProperty = DependencyProperty.Register("PointCount", typeof(int), typeof(Spirograph), new UIPropertyMetadata(100, new PropertyChangedCallback(PointCountPropertyChanged)));
private static void PointCountPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Spirograph spirograph = sender as Spirograph;
if (spirograph != null)
spirograph.PointCountPropertyChanged(e);
}
private void PointCountPropertyChanged(DependencyPropertyChangedEventArgs e)
{
bool running = Running;
Reset();
CalculatePoints();
if (running)
Start();
}
#region Delay
public int Delay
{
get { return (int)GetValue(DelayProperty); }
set { SetValue(DelayProperty, value); }
}
public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof(int), typeof(Spirograph), new UIPropertyMetadata(30, new PropertyChangedCallback(DelayPropertyChanged)));
private static void DelayPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Spirograph spirograph = sender as Spirograph;
if (spirograph != null)
spirograph.DelayPropertyChanged(e);
}
private void DelayPropertyChanged(DependencyPropertyChangedEventArgs e)
{
bool running = Running;
Stop();
_timer.Interval = TimeSpan.FromMilliseconds((int)e.NewValue);
if (running)
Start();
}
#endregion
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(Spirograph), new UIPropertyMetadata(10.0));
public Point StartPoint
{
get { return (Point)GetValue(StartPointProperty); }
set { SetValue(StartPointProperty, value); }
}
public static readonly DependencyProperty StartPointProperty = DependencyProperty.Register("StartPoint", typeof(Point), typeof(Spirograph), new UIPropertyMetadata(new Point(0, 0), new PropertyChangedCallback(StartPointPropertyChanged)));
private static void StartPointPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Spirograph spirograph = sender as Spirograph;
if (spirograph != null)
spirograph.StartPointPropertyChanged(e);
}
private void StartPointPropertyChanged(DependencyPropertyChangedEventArgs e)
{
bool running = Running;
Stop();
StartPoint = (Point)e.NewValue;
CalculatePoints();
if (running)
Start();
}
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(Spirograph), new UIPropertyMetadata(new SolidColorBrush(Colors.Blue)));
public Thickness StrokeThickness
{
get { return (Thickness)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(Thickness), typeof(Spirograph), new UIPropertyMetadata(new Thickness(1)));
public void Start()
{
if (!_loaded)
AutoStart = true;
else
{
Running = true;
_timer.Start();
}
}
public void Stop()
{
Running = false;
_timer.Stop();
}
public void Reset()
{
Stop();
_figure.Segments.Clear();
_pointIndex = 0;
}
private void CalculatePoints()
{
_points.Clear();
Point lastPoint = StartPoint;
double a = 0.0;
double rr = 0.5 * Radius;
for (int i = 0; i <= PointCount; i++)
{
Point pt = new Point();
pt.X = lastPoint.X + Radius * Math.Cos(a);
pt.Y = lastPoint.Y + Radius * Math.Sin(a);
_points.Add(pt);
double aa = -0.8 * a;
Point pnt = new Point();
pnt.X = pt.X + rr * Math.Cos(aa);
pnt.Y = pt.Y + rr * Math.Sin(aa);
a += 0.5;
_points.Add(pnt);
lastPoint = pnt;
}
}
}
}
Xaml of Window hosting the control
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
x:Name="MainWindowX">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Points:" Margin="5" />
<Slider x:Name="PointSlider" Orientation="Horizontal" Minimum="10" Maximum="10000" Value="1000" />
<Button Content="Start" Height="24" Margin="5" Click="StartClick" />
<Button Content="Stop" Height="24" Margin="5" Click="StopClick" />
<Button Content="Reset" Height="24" Margin="5" Click="ResetClick" />
<TextBlock Text="Delay:" Margin="5" />
<Slider x:Name="Slider" Orientation="Horizontal" Minimum="1" Maximum="500" Value="100" Height="50" />
</StackPanel>
<app:Spirograph x:Name="Spirograph" Grid.Column="1" PointCount="{Binding Value, ElementName=PointSlider}" Radius="50" AutoStart="False" Delay="{Binding Path=Value, ElementName=Slider}" />
</Grid>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.