2012-03-30 39 views
4

Ich habe die Grundidee, wie es gemacht werden kann, aber ich kenne den Code nicht wirklich. Ich möchte eine WPF-Anwendung in Visual Studio verwenden. Wenn der Benutzer auf eine Schaltfläche "Zeichnen" klickt, wird eine Form (ein Spirograph) auf der Leinwand gezeichnet (mit einer Polylinie), aber die Drehung ist, es muss Punkt für Punkt gezeichnet werden, eine Zeile nach der anderen Siehe diese "Animation". Außerdem sollte der Benutzer die Zeichnung abbrechen/stoppen können, während sie auf der Leinwand gezeichnet wird. Zuerst muss eine Liste oder ein Array von Punkten erstellt werden (ich bin mit Arrays vertrauter), und die Punkte werden dann an einen Hintergrundarbeiter übergeben, der "seinen Fortschritt meldet", indem er die Form langsam auf der Leinwand zeichnet. Hier ist der Code zum Zeichnen eines Spirographen, aber jede Form ist wirklich ok.Wie zeichnet man Punkt für Punkt in C#?

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

Antwort

5

Zuerst richten Sie Ihre Leinwand:

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

Dann müssen Sie Multi-Thread-Anwendung. Multi-Threading in WPF ist ein heikles Geschäft, da Sie nicht auf einen der Zeichnungskontexte eines anderen Threads zugreifen können. Glücklicherweise kann die Klasse BackgroundWorker Ihnen hier einige Probleme ersparen, da das Ereignis ProgressChanged auf demselben Thread ausgeführt wird. Also, wenn der Benutzer auf der Leinwand klickt:

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

Nun, da wir den Hintergrund Arbeiter eingerichtet haben, haben wir die meisten der schweren Heben innerhalb des DoWork Delegierten:

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

Hinweis, wie wir Verwenden Sie die ReportProgress Methode, um den Punkt zu übergeben; dies ermöglicht es uns, die Ausführung von Thread zuzugreifen und zu unserer Polylinie hinzufügen:

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

Nun ist die einzige Sache, die die Animation zu stoppen ist zu unterstützen bleibt. Ich entschied mich, dies mit einem Rechtsklick zu implementieren (Klicken mit der linken Maustaste zum Zeichnen, Rechtsklick zum Stoppen/Löschen). Sie können natürlich diese Kontrolle an diese Funktionalität anhängen. Hier ist die rechte Maustaste 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 
} 
1

Ich war ein wenig langweilig heute, so dass ich gepeitscht für Sie eine einfache Benutzersteuerung auf. Verwendet nur einen Timer, um es zu animieren. Die Abhängigkeitseigenschaften Delay/Radius/Point count, so dass Sie diese mit Dingen (wie Schiebereglern oder was auch immer) binden können.

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

Benutzer 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 von Window Hosting der Steuer

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