-1

I'm relatively new to WPF animations. I would like to have an object (a simple circle for example) move around a circular path in 10 degree increments. The following example is tantalizingly close: WPF circular moving object See the answer by Clemens who uses a RotateTransform in XAML and a DoubleAnimation in code-behind. I am however, using MVVM and it's not clear to me how to accomplish this. I believe I would need to access the RotateTransform which is in the View from my ViewModel, so I can call BeginAnimation, but how?

Any examples or ideas? I have searched without luck. Thanks

UPDATE: I can now be more specific in my question by showing what I've already tried. Based on the above mentioned reference AND Twoway-bind view's DependencyProperty to viewmodel's property? (answer by @Michael Schnerring), I have the following simple code (below). My ellipse is not rotating. No binding errors, or any other errors, just no rotation. And my methods are hit (I debugged into it) I'm guessing my PerformAnimation function is incorrect, specifically the SetTargetProperty part. I did try to play with it by adding two animations (one for Rotation, one for Transform) but without luck.

Can someone give me an idea what I'm doing wrong?

XAML:

<Canvas Grid.Row="0" Grid.Column="0">
        <Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
         Canvas.Left="200" Canvas.Top="200"
         RenderTransformOrigin="0.5,0.5">
            <Ellipse.RenderTransform>
                <TransformGroup>
                    <TranslateTransform Y="-100"/>
                <RotateTransform />
                </TransformGroup>
            </Ellipse.RenderTransform>
        </Ellipse>
    </Canvas>

    <Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>

Code-behind

  public partial class MainWindow : Window
   {
      public MainWindow()
      {
         InitializeComponent();
         var nameOfPropertyInVm = "AngleProperty";
    var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
         this.SetBinding(AngleProperty, binding);
      }

      public double Angle
      {
         get { return (double)GetValue(AngleProperty); }
         set { SetValue(AngleProperty, value); }
      }

      // Using a DependencyProperty as the backing store for Angle.  This enables animation, styling, binding, etc...
      public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));

      private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
      {
         MainWindow control = (MainWindow)sender;
         control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
      }

      private void PerformAnimation(double oldValue, double newValue)
      {
         Storyboard s = new Storyboard();

         DoubleAnimation animation = new DoubleAnimation();
         animation.From = oldValue;
         animation.To = newValue;
         animation.Duration = new Duration(TimeSpan.FromSeconds(1));
         s.Children.Add(animation);

         
         Storyboard.SetTarget(animation, MyEllipse);
         Storyboard.SetTargetProperty(animation, new PropertyPath("(Ellipse.RenderTransform).(RotateTransform.Angle)"));

         s.Begin();

      }

And ViewModel

    public class MyViewModel :  ViewModelBase
   {
      private ICommand _rotateCommand = null;
      private double _angleProperty = 10;
      public MyViewModel()
      {
         
      }

      public double AngleProperty
      {
         get
         {
            return _angleProperty;
         }

         set
         {
            _angleProperty = value;
            OnPropertyChanged("AngleProperty");
         }
      }

      public ICommand RotateCommand
      {
         get
         {
            if (_rotateCommand == null)
            {
               _rotateCommand = new RelayCommand(param => RotateCommandImplementation());
            }
            return _rotateCommand;
         }
      }

      private void RotateCommandImplementation()
      {
         AngleProperty = AngleProperty + 10;
      }
   }
Dave
  • 8,095
  • 14
  • 56
  • 99
  • 1
    The RotateTransform's Angle property could be bound to a property in your view model. For starting an animation, there should be a dependency property (e.g. in a UserControl or an attached property) with a PropertyChangedCallback that starts an animation. That property would be bound to a "target angle" property in your view model. – Clemens Feb 02 '22 at 07:05
  • Thanks for the response. Based on your comment, I've almost got it working :). Perhaps since it's now a direct question, and I have the sample code, I can simply post. I suspect the problem is I'm not taking into account the TranslateTransform and RotateTransform correctly. I'm a little confused on your example what the TranslateTransform Y="-100" does, and I'll try to figure that out first. Getting there slowly. Thanks! – Dave Feb 02 '22 at 20:31
  • Updated question to show what I have already tried! – Dave Feb 02 '22 at 22:17
  • 1
    I don't get how you managed to introduce that Storybord. In my answer to the other question there is `` and `rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);`. Be aware that `"(Ellipse.RenderTransform).(RotateTransform.Angle)"` is not a valid property path here. It tries to set the Angle of a RotateTransform in the RenderTransform of the Ellipse. The RenderTransform however is a TransformGroup. You have to understand what you are writing there, not just copy it from somewhere and do some trial and error. – Clemens Feb 02 '22 at 23:51
  • Eureka! I got it. Apparently I added the Storybord to just to make it more complicated :). I took it out and cleaned it up. I'll post my code as an answer but by all means, throw your comments into an answer and I'll accept that. I really appreciate the help and patience! About the only thing wrong now (which isn't particularly a problem in my case) is that the ellipse rotates once by 10 degrees as soon as the program starts. Probably the binding is happening and I hit my PerformAnimation code. – Dave Feb 03 '22 at 00:03

1 Answers1

1

Here's my solution, based on a lot of help from @Clemens (see comments and WPF circular moving object)

VIEW

<Window x:Class="AnimationBindingPlay.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:AnimationBindingPlay"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Canvas Grid.Row="0" Grid.Column="0">
        <Ellipse Height="100" Width="100" Fill="Aqua" Name="MyEllipse"
         Canvas.Left="200" Canvas.Top="200"
         RenderTransformOrigin="0.5,0.5">
            <Ellipse.RenderTransform>
                <TransformGroup>
                    <TranslateTransform Y="-100"/>
                    <RotateTransform x:Name="rotateTransform"/>
                </TransformGroup>
            </Ellipse.RenderTransform>
        </Ellipse>
    </Canvas>

    <Button Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding Path=RotateCommand}">Press Me!</Button>
    
</Grid>

Code behind

 public partial class MainWindow : Window
   {
      public MainWindow()
      {
         InitializeComponent();
         var nameOfPropertyInVm = "AngleProperty";
         var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
         this.SetBinding(AngleProperty, binding);
      }

      public double Angle
      {
         get { return (double)GetValue(AngleProperty); }
         set { SetValue(AngleProperty, value); }
      }

      // Using a DependencyProperty as the backing store for Angle.  This enables animation, styling, binding, etc...
      public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0, new PropertyChangedCallback(AngleChanged)));

      private static void AngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
      {
         MainWindow control = (MainWindow)sender;
         control.PerformAnimation((double)e.OldValue, (double)e.NewValue);
      }

      private void PerformAnimation(double oldValue, double newValue)
      {
       
         var rotationAnimation = new DoubleAnimation(oldValue, newValue, TimeSpan.FromSeconds(1));
         rotateTransform.BeginAnimation(RotateTransform.AngleProperty, rotationAnimation);

      }

   }

ViewModel Same as in question!

Dave
  • 8,095
  • 14
  • 56
  • 99