我有基本的想法,如何做到但我真的不知道代码。我想在Visual Studio中使用WPF应用程序。当用户点击“绘图”按钮时,它会在画布上绘制一个形状(一个螺旋形图)(使用折线),但扭曲是,它需要逐点绘制,一次一行,所以你将看到这个“动画”。此外,用户应该能够在画布上绘制时取消/停止绘图。首先,它需要生成一个列表或点数组(我对数组更熟悉),然后将这些点传递给后台工作者,后者将通过在画布上缓慢绘制形状来“报告其进度”。这是绘制螺旋形图的代码,但任何形状都可以。
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);
}
}
答案 0 :(得分: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 :(得分: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 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>