[英]How to draw a shape point by point in C#?
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. 我想在Visual Studio中使用WPF应用程序。 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. WPF中的多线程是一个冒险的业务,因为您无法从不同的线程访问任何绘图上下文。 Fortunately, the BackgroundWorker
class can save you some headaches here, as its ProgressChanged
event runs on the same thread. 幸运的是, BackgroundWorker
类可以在这里省去一些麻烦,因为它的ProgressChanged
事件在同一个线程上运行。 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: 现在我们已经设置了后台工作程序,我们在DoWork
完成了大部分繁重工作:
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; 注意我们如何使用ReportProgress
方法传递指出; 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 用户控制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 承载控件的Xaml of Window
<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>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.