简体   繁体   English

如何在C#中逐点绘制形状?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM