22 July 2013

Extension methods to animate an object over the screen via waypoints for Windows Phone

A long time ago, before I even was a Microsoft MVP, I wrote an article about a behavior that could make anything draggable and flickable for Windows Phone (then still 7). That article used a few extension methods to FrameworkElement and Storyboard to create the desired effect Those extension methods are a bit limited, because they can only create an animation that move an object from one location to another – there are no possible points in between.

The Windows Phone 8 game I am currently developing needs just that, and as usual, as soon as I created something reusable, I spin it off to my library and start blogging about it. Don’t despair if you don’t get the whole explanation – I explain how I created the code and what it does, but if you don’t care, you can just copy it and use the resulting calls.

The earlier extension methods created a DoubleAnimation. Well actually it created two – one to animate the TranslateX property of a CompositeTransform, the other to animate the TranslateY property, over a certain Duration. But these kinds of ‘simple’ animations are executed in parallel in a Storyboard – and now I want to do consecutive animations. You then need key frames. And since the Translate properties are doubles, you need to create instances of DoubleAnimationUsingKeyFrames :). Luckily, the Microsoft developers creating this API did choose some logical names. There are a few things you should remember:

  • The DoubleAnimationUsingKeyFrames animates the property, so this needs to be used in the SetTarget of the Storyboard
  • For the individual animation frames you need to add LinearDoubleKeyFrame instances to the DoubleAnimationUsingKeyFrames’ KeyFrames collection – with a key time and a value. This is the value which the animated property needs to have at the key time.
  • The duration of the entire DoubleAnimationUsingKeyFrames needs to be equal to the sum of key times of the individual LinearDoubleKeyFrame key times.
  • We need to animate both X and Y, so there needs to be two animations per key time, namely one for X and one for Y

Let’s do that!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Wp7nl.Utilities
{
  public static class StoryboardExtensions2
  {
    public static Timeline CreateKeyFrameAnimation(this Storyboard storyboard, 
        IList<double> values, IList<Duration>times)
    {
      var keyFrameAnimation = new DoubleAnimationUsingKeyFrames();
      var keyTime = TimeSpan.FromMilliseconds(0);
      for (var i = 0 ; i< values.Count(); i++)
      {
        keyTime += times[i].TimeSpan;
        var frame = new LinearDoubleKeyFrame {Value = values[i], KeyTime = keyTime};
        keyFrameAnimation.KeyFrames.Add(frame);
        keyFrameAnimation.Duration += times[i];
      }
      return keyFrameAnimation;
    }
  }
}

This code is still pretty abstract, but it’s doing exactly what I described – it makes the DoubleAnimationUsingKeyFrames, adds the keyframes (of type LinearDoubleKeyFrame  - I found that out using Blend and basically translated the XAML to C#) and makes the total duration equal to the sum of the durations.The first key frame on key time 0 is the first point – and from then we add more points and new key times.

This, of course, is not very “programmer friendly”. That is where the main extension method comes in:

public static void AddWayPointAnimation(
  this Storyboard storyboard, FrameworkElement fe, 
  IList<Point> points, double speed)
{
  var durations = new List<Duration> {new Duration(TimeSpan.FromSeconds(0))};
  for (var i = 0; i < points.Count - 1; i++)
  {
    durations.Add(points[i].CalculateDuration(points[i + 1], speed));
  }
  var xValues = points.Select(p => p.X).ToList();
  storyboard.AddAnimation(fe.RenderTransform, 
    storyboard.CreateKeyFrameAnimation(xValues, durations), 
       CompositeTransform.TranslateXProperty);
  var yValues = points.Select(p => p.Y).ToList();
  storyboard.AddAnimation(fe.RenderTransform, 
    storyboard.CreateKeyFrameAnimation(yValues, durations), 
      CompositeTransform.TranslateYProperty);
}

So you pass in an empty Storyboard, the GUI element you want to animate, a list of waypoints, and a speed (in pixels per second). And done. You can now simply use the following code:

myGuiElement.RenderTransform = new CompositeTransform();
myGuiElement.RenderTransformOrigin = new Point(0.5, 0.5);
var s = new Storyboard();
s.AddWayPointAnimation(myGuiElement, myListOfPoint, 500);
s.Begin();

And whatever is in myGuiElement will be animated over the waypoints. The first two statements can also be replaced by creating the transform in Blend. You may, by the way, have noticed some odd statements in AddWayPointAnimation. Both the red underlined statements are not part of the standard API. CalculateDuration is part of a few extension methods for Point that I wrote some time ago, and AddAnimation is part of the original article I mentioned before. Both are now part of my #wp7nl library on codeplex (and its accompanying NuGet package) so I won’t go into details about this.

screenshot4To demonstrate this principle I have made the following pinnacle of animation design:

When you hit the button “move”it will move the whole TitlePanel first a bit to the left, then down, then to the right, and finally up to where it came from. Pretty pointless to do this from code, but it proves the point.

Note that what we animate here is a Translation. So the coordinates used are relative coordinates within the container!

As usual, the full demo solution can be downloaded here

03 July 2013

On-the-fly updating of a data template selected by a DataTemplateSelector

It’s really fun when someone contacts you with a Windows Phone development problem and you have already something standing by to solve it – even if it was intended for something completely different. Last week fellow MVP Mark Monster contacted me with the following situation:

  • He had several objects in an ObservableCollection
  • He was using a DataTemplateSelector to determine which template was to be used for an object – I assume he used something like this.

This worked very fine when initially binding the list of objects to the LongListSelector, but he ran into the following problem: the template to be used was determined by an attribute that could be changed while the object was visible. Like, in the example by the article on geekchamp.com that I referred to, you could change the Food Type from healthy to unhealthy after the list was displayed. But if Mark did that, the template stayed the same. The new template was not selected.

This is of course perfectly understandable – the template selector, being a ContentControl, just has as OnContentChanged you can override - but has no knowledge about what is going on in the collection from which its instances are created.

The solution is very simple, albeit a bit crude – reaching back into the last post of my 4-part mapping series, I pulled out this brilliant *cough* piece of coding, originally cobbled together to compensate for a bug I in my own CodePlex library:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace Wp7nl.Utilities
{
  public class ResettableObservableCollection<T> : ObservableCollection<T>
  {
    public ResettableObservableCollection()
    {
    }

    public ResettableObservableCollection(List<T> list)
      : base(list)
    {
    }

    public ResettableObservableCollection(IEnumerable<T> list)
      : base(list)
    {
    }

    public void ForceReset()
    {
      OnCollectionChanged(
       new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
  }
}

The only thing Mark needed to do was:

  • Change his standard ObservableCollection into a ResettableObservableCollection,
  • Call “ForceReset” on the collection after he had changed the attribute that should trigger the template change.

This will work on Windows Phone 8 as well on Windows Phone 7. It’s crude, but effective. Sometimes life is so simple - so simple in fact that that I hope you will forgive me for omitting a demo solution this time. I hope this helps more people wrestling with the same problem.

Which reminds me I should update my wp7nl library on CodePlex again. The additions are backing up again ;-)

02 July 2013

Behavior to hide UI elements when a bound collection is empty

screenshot1This is a fun little thingy I wrote when dealing with error lists. Suppose, you want the user to be able to see there are errors, but not directly fly them the whole detailed error list in his face. So there is, for instance a button “Show errors”, like in the application showed to the right.

But, it’s ugly “Show errors” is always visible, even if there are no errors to display. I actually only want to have this button appear when there are errors indeed, so that it acts as an error indicator, and then the user can decide if she wants to see them or not. Of course you can fix that in your viewmodel – subscribe to the events of an ObservableCollection of errors, and turn visibility on or off when trapping those events, every time you need to do this. Perfectly viable solution. But an even better solution is to encapsulate that behavior – the word just says it – in a simple piece of reusable code.

Meet HideWhenCollectionEmptyBehavior. It sports an INotifyCollectionChanged Collection to which you can bind, and the rest of it is actually so simple I am going to show it in one go:

using System.Collections;
using System.Collections.Specialized;
using System.Windows;

namespace Wp7nl.Behaviors
{
  public class HideWhenCollectionEmptyBehavior : SafeBehavior<FrameworkElement>
  {
    protected override void OnSetup()
    {
      base.OnSetup();
      SetVisibility();
    }

    protected override void OnCleanup()
    {
      base.OnCleanup();
      if (Collection != null)
      {
        Collection.CollectionChanged -= OnCollectionChanged;
      }
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      SetVisibility();
    }

    private void SetVisibility()
    {
      var collection = Collection as ICollection;
      AssociatedObject.Visibility = 
        collection != null && collection.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
    }
  }
 }

The behavior is implemented as a SafeBehavior with an attached dependency property “Collection” of type INotifyPropertyChanged to which you can bind the collection that needs to be monitored. The core of the whole behavior is simply the SetVisibility method, which casts the collection to ICollection and checks if it’s null or empty – in that case the Visibility of the object to which this behavior is attached is set to Collapsed – if not, it’s set to Visible.

The attached dependency property is fairly standard, apart from the last part:

#region Collection

public const string CollectionPropertyName = "Collection";

public INotifyCollectionChanged Collection
{
  get { return (INotifyCollectionChanged)GetValue(CollectionProperty); }
  set { SetValue(CollectionProperty, value); }
}

public static readonly DependencyProperty CollectionProperty = DependencyProperty.Register(
    CollectionPropertyName,
    typeof(INotifyCollectionChanged),
    typeof(HideWhenCollectionEmptyBehavior),
    new PropertyMetadata(default(INotifyCollectionChanged), OnCollectionChanged));

public static void OnCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var behavior = d as HideWhenCollectionEmptyBehavior;
  var newValue = (INotifyCollectionChanged)e.NewValue;
  var oldValue = (INotifyCollectionChanged)e.OldValue;
  if (behavior != null)
  {
    if (oldValue != null)
    {
      oldValue.CollectionChanged -= behavior.OnCollectionChanged;
    }
    if (newValue != null)
    {
      newValue.CollectionChanged += behavior.OnCollectionChanged;
    }

    behavior.SetVisibility();
  }
}

#endregion

There’s a lot of plumbing going on ‘just to be on the safe side’ – if the bound collection is replaced, the CollectionChanged event is detached from the old collection and attached to the new collection. Everything to prevent memory leaks:-) - but in reality I think only the newValue will ever be set.But anyway, by making the property of type INotifyCollectionChanged, I am sure to have the lowest common denominator and still have a CollectionChanged that I can trap.

By dragging the behavior on top of the “Show Errors”  button and binding to the collections of errors, my app initially looks like displayed on the left. Only when I click “add errors” the “show errors” button appears (courtesy of HideWhenCollectionEmptyBehavior) and then when I click it, I get to see the actual errors.

screenshot3

screenshot1

screenshot2

 

 

 

 

 

 

 

Now of course this is a pretty contrived example, but it is a real-word use case. Like I wrote, I actually use it for indicating that there are errors, but I can think of a lot of other scenarios, for instance an UI element that is displayed when an object has child objects (say, an order has order lines) without actually displaying them – only indicating they are present.

I wrote this behavior to act in a Windows Phone application but the code is so generic it will work in other XAML platforms as well, including Windows 8.

The code of the behavior, together with my beautiful *cough* sample app can be found here

For those who’d like me to use “Any()” in stead of “Count > 0 “ I would like to point out that ICollection is just ICollection, not ICollection<T> and that does not seem to support “Any()”