2012-03-30 28 views
4

我有如何完成这项工作的基本想法,但我并不真正了解代码。我想在Visual Studio中使用WPF应用程序。当用户点击一个“绘制”按钮时,它会在画布上绘制一个形状(一个轮廓图)(使用多段线),但扭曲是,它需要逐点绘制一条线,所以你会看到这个“动画”。另外,用户应该可以在画布上绘制时取消/停止绘图。首先,它需要生成一个列表或数组(我对数组更加熟悉),然后将这些点传递给后台工作人员,通过在画布上缓慢地绘制形状来“报告其进度”。这里是绘制螺旋形图的代码,但任何形状都可以。如何在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); 
    } 
} 

回答

5

首先,设置画布:

<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

今天我有点无聊,所以我为你提供了一个简单的用户控件。只需使用一个计时器来为其设置动画。延迟/半径/点计数依赖项属性,所以你可以绑定到他们的东西(如滑块或其他)。

用户控制的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窗口的承载控制

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