Как нарисовать фигуру точка за точкой в ​​C #? - PullRequest
4 голосов
/ 30 марта 2012

У меня есть базовое представление о том, как это можно сделать, но я действительно не знаю код. Я хочу использовать приложение WPF в Visual Studio. Когда пользователь нажимает кнопку «Рисовать», он рисует фигуру (спирограф) на холсте (используя ломаную линию), но поворот заключается в том, что ему нужно рисовать его по точкам, по одной линии за раз, чтобы увидеть эту «анимацию». Кроме того, пользователь должен иметь возможность отменить / остановить рисунок во время его рисования на холсте. Сначала нужно будет создать список или массив точек (я больше знаком с массивами), а затем передать точки фоновому работнику, который "сообщит о своем прогрессе", медленно рисуя фигуру на холсте. Вот код для рисования спирографа, но любая форма в порядке.

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);
    }
}

Ответы [ 2 ]

5 голосов
/ 30 марта 2012

Сначала настройте свой холст:

<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>

Затем вам понадобится многопоточное приложение.Многопоточность в WPF - дело рискованное, потому что вы не можете получить доступ к любому контексту рисования из другого потока.К счастью, класс BackgroundWorker может избавить вас от некоторых головных болей, поскольку его событие ProgressChanged выполняется в том же потоке.Итак, когда пользователь нажимает на холст:

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 );
}

Теперь, когда мы настроили фонового рабочего, мы выполняем большую часть тяжелой работы внутри делегата 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;
  }
}

Обратите внимание, как мы используем метод ReportProgress, чтобы передать точку;это позволит нам получить доступ к исполняющему потоку и добавить к нашей ломаной линии:

private void AnimationWorkerOnProgressChanged( object sender, ProgressChangedEventArgs progressChangedEventArgs ) {
  var p = (Point) progressChangedEventArgs.UserState;
  Poly.Points.Add( p );
}

Теперь единственное, что остается, - это остановить анимацию.Я решил реализовать это по щелчку правой кнопкой мыши (щелчок левой кнопкой мыши, чтобы нарисовать, щелчок правой кнопкой мыши, чтобы остановить / очистить).Вы, конечно, можете прикрепить любой элемент управления к этой функции.Вот обработчик правой кнопки мыши:

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
}
1 голос
/ 31 марта 2012

Мне было немного скучно сегодня, поэтому я выбрал для вас простой пользовательский элемент управления. Просто использует таймер для его анимации. Свойства зависимостей отсчета Задержка / Радиус / Точка, так что вы можете связать их с чем-либо (например, с ползунками или чем-то еще).

Пользовательский контроль 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>

Код контроля пользователя

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, управляющий элементом управления

<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>

...