トップ回答者
Polylineの変更をアニメーションしたい

質問
回答
-
BeginAnimationでPointAnimationを渡すだけ。
#面倒なのでMVVMもビヘイビア化もしてない。必要なら先のコードを参考に書き換えてください。
<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="525" Width="525"> <Grid> <Canvas> <Canvas.LayoutTransform> <ScaleTransform ScaleY="-1" /> </Canvas.LayoutTransform> <Path x:Name="path" Stroke="Red" Tag="{Binding .,NotifyOnTargetUpdated=True}" TargetUpdated="path_TargetUpdated"> </Path> </Canvas> <Button Content="Test" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Bottom"/> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Media.Animation; using System.Collections.ObjectModel; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); for (int i = 0; i < 10; i++) { ps.Add(new Point(i * 50, i * 50)); } this.DataContext = ps; } private ObservableCollection<Point> ps = new ObservableCollection<Point>(); private void Button_Click(object sender, RoutedEventArgs e) { Random rnd = new Random(); ps[rnd.Next(ps.Count)] = new Point(rnd.Next(500), rnd.Next(500)); } private void path_TargetUpdated(object sender, DataTransferEventArgs e) { var path = e.TargetObject as Path; var inc = path.Tag as System.Collections.Specialized.INotifyCollectionChanged; inc.CollectionChanged += inc_CollectionChanged; var ie = inc as IEnumerable<Point>; PathFigure col = new PathFigure(); col.StartPoint = ie.First(); foreach (Point p in ie.Skip(1)) { col.Segments.Add(new LineSegment(p, true)); } PathGeometry pg = new PathGeometry(); pg.Figures = new PathFigureCollection() { col }; path.Data = pg; } private void inc_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) { Random rnd = new Random(); Duration d = new Duration(TimeSpan.FromSeconds(5)); PathFigure pf = ((PathGeometry)this.path.Data).Figures[0]; int index = e.NewStartingIndex; foreach (Point p in e.NewItems) { PointAnimation pa = new PointAnimation(p, d); if (index == 0) { pf.BeginAnimation(PathFigure.StartPointProperty, pa); } else { var ls = (LineSegment)(pf.Segments[index - 1]); ls.BeginAnimation(LineSegment.PointProperty, pa); } index++; } } } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答の候補に設定 星 睦美 2016年9月14日 0:50
- 回答としてマーク WPF_Visiter 2016年9月14日 4:10
すべての返信
-
Pathなら各点がDependencyPropertyなのでPointAnimationでアニメーション化できるんですけど、PolylineのPointsに入れるPointCollectionの要素はDependencyPropertyではないので標準ではアニメーション対象にはできないのです…
なので、クロックを用意して各時点での位置をPointsに自前で反映させてやることになります。
<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="600" Width="600"> <Window.Resources> <app:ColorToSolidBrush x:Key="brushConverter" /> </Window.Resources> <Grid Background="LightGray"> <ItemsControl ItemsSource="{Binding Path=Lines}" Margin="20" Background="White"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > <Canvas.LayoutTransform> <TransformGroup> <ScaleTransform ScaleY="-1" /> </TransformGroup> </Canvas.LayoutTransform> </Canvas> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Polyline Stroke="{Binding Path=Color,Converter={StaticResource brushConverter}}" StrokeThickness="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" app:PolylineAnimator.Points="{Binding Path=Points}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Button Content="Test" HorizontalAlignment="Center" VerticalAlignment="Bottom" Command="{Binding Path=TestCommand}"/> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Windows.Media.Animation; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = Model.CreateTestModel(); } } class Model : System.ComponentModel.INotifyPropertyChanged { public static Model CreateTestModel() { var m = new Model(); foreach (Color c in new Color[] { Colors.Red, Colors.Blue }) { Line line = new Line(); line.Color = c; for (int i = 0; i < 10; i++) { line.Points.Add(new Point(i * 50, 0)); } m.Lines.Add(line); } return m; } public Model() { Lines = new ObservableCollection<Line>(); TestCommand = new Command() { m = this }; } public ObservableCollection<Line> Lines { get; private set; } public ICommand TestCommand { get; private set; } class Command : ICommand { public Model m; public bool CanExecute(object parameter) { return true; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { Random rnd = new Random(); foreach (Line line in m.Lines) { int index = rnd.Next(line.Points.Count); //for (int index = 0; index < m.Points.Count; index++) { line.Points[index] = new Point(line.Points[index].X, rnd.NextDouble() * 500); } } } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; } class Line { public Line() { Points = new ObservableCollection<Point>(); } public ObservableCollection<Point> Points { get; private set; } public Color Color { get; set; } } class ColorToSolidBrush : System.Windows.Data.IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { try { return new SolidColorBrush((Color)value); } catch { return null; } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { SolidColorBrush brush = value as SolidColorBrush; if (brush != null) { return brush.Color; } throw new NotSupportedException(); } } class PolylineAnimator { public static IEnumerable<Point> GetPoints(DependencyObject obj) { return (IEnumerable<Point>)obj.GetValue(PointsProperty); } public static void SetPoints(DependencyObject obj, IEnumerable<Point> value) { obj.SetValue(PointsProperty, value); } public static readonly DependencyProperty PointsProperty = DependencyProperty.RegisterAttached("Points", typeof(IEnumerable<Point>), typeof(PolylineAnimator) , new FrameworkPropertyMetadata(null, OnPointsChanged)); private static readonly DependencyProperty AnimatorProperty = DependencyProperty.RegisterAttached("Animator", typeof(PolylineAnimator), typeof(PolylineAnimator), new PropertyMetadata(null)); private static void OnPointsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Polyline poly = d as Polyline; if (poly == null) { return; } var animator = (PolylineAnimator)poly.GetValue(AnimatorProperty); if (animator != null) { animator.Disconnect(); poly.ClearValue(AnimatorProperty); } if (e.NewValue != null) { var ie = e.NewValue as IEnumerable<Point>; if (ie != null) { poly.Points = new PointCollection(ie); var inc = ie as INotifyCollectionChanged; if (inc != null) { animator = new PolylineAnimator(poly, inc); poly.SetValue(AnimatorProperty, animator); } } } } private Duration duration;//アニメーション時間 private Polyline Polyline;//操作対象 private INotifyCollectionChanged SourceCollection;//PolylineのPointsに渡す前のPointのコレクション private Dictionary<AnimationClock, AnimationItem> dic = new Dictionary<AnimationClock, AnimationItem>(); public PolylineAnimator(Polyline poly, INotifyCollectionChanged sourceCollection) { this.Polyline = poly; this.SourceCollection = sourceCollection; this.SourceCollection.CollectionChanged += OnCollectionChanged;//本当はWeakeventでするべき this.duration = new Duration(TimeSpan.FromSeconds(3)); } public void Disconnect() { foreach (var item in this.dic.Values) { item.Stop(); } this.dic.Clear(); this.SourceCollection.CollectionChanged -= OnCollectionChanged; } public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { //ソースのコレクションのPointが操作された if (e.Action == NotifyCollectionChangedAction.Replace) { int startIndex = e.NewStartingIndex; int endIndex = e.NewStartingIndex + e.NewItems.Count - 1; Dictionary<int, Point> currentPoints = new Dictionary<int, Point>(); foreach (AnimationClock ac in dic.Keys.ToArray()) { //操作対象のインデックスが重なっていたら前の操作から外す var item = dic[ac]; for (int i = item.Animations.Count - 1; i >= 0; --i) { IndexedPointAnimation ipa = item.Animations[i]; if (startIndex <= ipa.Index && ipa.Index <= endIndex) { Point p = item.GetCurrentPoint(ipa); currentPoints.Add(ipa.Index, p); item.Animations.RemoveAt(i); } } if (item.Animations.Count == 0) { dic.Remove(ac); ac.Controller.Stop(); ac.Controller.Remove(); } } AnimationItem animationItem = null; for (int i = 0; i < e.NewItems.Count; i++) { //操作されたPointのアニメーションを作る int index = i + e.NewStartingIndex; Point old = (Point)e.OldItems[i]; if (currentPoints.ContainsKey(index)) { old = currentPoints[index];//アニメーション中なら現在位置からアニメーションさせる } var ipa = new IndexedPointAnimation(index, old, (Point)e.NewItems[i], this.duration); if (i == 0) { var ac = ipa.CreateClock(); animationItem = new AnimationItem(this, ac); dic.Add(ac, animationItem); } animationItem.Animations.Add(ipa); } } else {//複雑な変更には対応してません foreach (var item in this.dic.Values) { item.Stop(); } this.dic.Clear(); var ie = Polyline.Tag as IEnumerable<Point>; if (ie != null) { Polyline.Points = new PointCollection(ie); } } } /// <summary>アニメーション操作用</summary> class AnimationItem { public AnimationItem(PolylineAnimator parent, AnimationClock ac) { this.parent = parent; this.PointCollection = parent.Polyline.Points; this.Clock = ac; ac.CurrentTimeInvalidated += ac_CurrentTimeInvalidated; ac.Completed += ac_Completed; } public event EventHandler Completed; private PolylineAnimator parent; private PointCollection PointCollection; private AnimationClock Clock; public readonly List<IndexedPointAnimation> Animations = new List<IndexedPointAnimation>(); public Point GetCurrentPoint(IndexedPointAnimation ipa) { return (Point)ipa.GetCurrentValue(ipa.From, ipa.To, this.Clock); } public void ac_CurrentTimeInvalidated(object sender, EventArgs e) {//アニメーションで時間経過 Update(); } public void ac_Completed(object sender, EventArgs e) {//アニメーション完了 Update(); parent.dic.Remove(this.Clock); } private void Update() { //アニメーション結果を反映 foreach (IndexedPointAnimation ipa in Animations) { PointCollection[ipa.Index] = GetCurrentPoint(ipa); } } public void Stop() { this.Clock.Controller.Remove(); parent.dic.Remove(this.Clock); } } /// <summary>操作するIndexを持っているPointAnimation</summary> class IndexedPointAnimation : PointAnimation { public IndexedPointAnimation(int index, Point fromPoint, Point toPoint, Duration d) : base(fromPoint, toPoint, d) { this.Index = index; } public int Index { get; private set; } } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 編集済み gekkaMVP 2016年9月11日 13:42 PolylineのPointsの説明が不十分だったので追記
-
BeginAnimationでPointAnimationを渡すだけ。
#面倒なのでMVVMもビヘイビア化もしてない。必要なら先のコードを参考に書き換えてください。
<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="525" Width="525"> <Grid> <Canvas> <Canvas.LayoutTransform> <ScaleTransform ScaleY="-1" /> </Canvas.LayoutTransform> <Path x:Name="path" Stroke="Red" Tag="{Binding .,NotifyOnTargetUpdated=True}" TargetUpdated="path_TargetUpdated"> </Path> </Canvas> <Button Content="Test" Click="Button_Click" HorizontalAlignment="Center" VerticalAlignment="Bottom"/> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Media.Animation; using System.Collections.ObjectModel; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); for (int i = 0; i < 10; i++) { ps.Add(new Point(i * 50, i * 50)); } this.DataContext = ps; } private ObservableCollection<Point> ps = new ObservableCollection<Point>(); private void Button_Click(object sender, RoutedEventArgs e) { Random rnd = new Random(); ps[rnd.Next(ps.Count)] = new Point(rnd.Next(500), rnd.Next(500)); } private void path_TargetUpdated(object sender, DataTransferEventArgs e) { var path = e.TargetObject as Path; var inc = path.Tag as System.Collections.Specialized.INotifyCollectionChanged; inc.CollectionChanged += inc_CollectionChanged; var ie = inc as IEnumerable<Point>; PathFigure col = new PathFigure(); col.StartPoint = ie.First(); foreach (Point p in ie.Skip(1)) { col.Segments.Add(new LineSegment(p, true)); } PathGeometry pg = new PathGeometry(); pg.Figures = new PathFigureCollection() { col }; path.Data = pg; } private void inc_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) { Random rnd = new Random(); Duration d = new Duration(TimeSpan.FromSeconds(5)); PathFigure pf = ((PathGeometry)this.path.Data).Figures[0]; int index = e.NewStartingIndex; foreach (Point p in e.NewItems) { PointAnimation pa = new PointAnimation(p, d); if (index == 0) { pf.BeginAnimation(PathFigure.StartPointProperty, pa); } else { var ls = (LineSegment)(pf.Segments[index - 1]); ls.BeginAnimation(LineSegment.PointProperty, pa); } index++; } } } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答の候補に設定 星 睦美 2016年9月14日 0:50
- 回答としてマーク WPF_Visiter 2016年9月14日 4:10