Showing posts with label Metro. Show all posts
Showing posts with label Metro. Show all posts

01 April 2012

Porting the DragFlickBehavior from Windows Phone 7 to Windows 8 Metro Style

Preface

A little over a year ago I made DragFlickBehavior, a behavior for Windows Phone that makes essentially anything draggable and ‘flickable’, that is, you can drag a GUI element along with your finger and it seems to have a little inertia when you let it go. In my previous post, I described the basics of how to make a behavior at all for Windows 8 Metro style. The testing of this was done using a ported version of the DragFlickBehavior. I’ve retraced my steps to how I got it to work, and will describe the process of porting an existing behavior here.

For the DragFlickBehavior to work, some groundwork needed to be layed first. For Windows Phone, I made a couple of extension methods for both FrameworkElement and StoryBoard first. To make matters worse, one of those extension methods in FrameworkElementExtensions used yet another extension method – GetVisualParent in VisualTreeHelperExtensions from Phone7.Fx… Nil desperandum… I’ll start at the beginning

Porting  VisualTreeHelperExtensions

I created a class library Win8nl.External, copied VisualTreeHelperExtensions.cs from it’s codeplex location, and opened the it the editor. And then the process was pretty simple:

  • The namespace System.Windows.Media is gone. So I deleted it’s using.
  • I basically clicked every red line, hit Control-.  (that’s Control-dot) and in most cases the editor would suggest a name space to add

In the end I seemed to have added

using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;

And then there was this slight matter of 2 places where the author calls VisualStateManager.GetVisualStateGroups and expects the result to be an IList. Now it’s an IEnumerable. Anyway, I solved this by changing the

IList groups = VisualStateManager.GetVisualStateGroups(root);

into

var groups = VisualStateManager.GetVisualStateGroups(root);

on both occasions. One file done. I won’t even pretend I understand what all those methods in this file are actually doing. I just ported them.

Porting FrameworkElementExtensions

I then created a library Win8nl, added references to Win8nl.External and WinRtBehaviors, and started on the FrameworkElementExtensions . This proved to be a pretty trivial matter. I needed to remove

using System.Windows.Controls;
using System.Windows.Media;
using System.Linq;
using Phone7.Fx;

And add after Control-dotting trough the errors I found I had added

using Windows.UI.Xaml.Media;
using Windows.UI.Xaml;
using Windows.Foundation
using Windows.UI.Xaml.Controls

Two files done!

Porting StoryboardExtensions

Routine starts to settle in. Remove

using System.Windows.Media;
using System.Windows.Media.Animation;
Control-dot around, and you will see you've added
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;

But then we hit our first snag. Two methods use a parameter of type IEasingFunction, that does no longer exist. But that can be fixed, by changing it into EasingFunctionBase.

Then I found out that Storyboard.SetTargetProperty apparently no longer wants to have a PropertyPath – which can be made from a DependencyProperty object – but a string. So method

public static void AddAnimation(this Storyboard storyboard,
 DependencyObject item, Timeline t, DependencyProperty p)
{
  if (p == null) throw new ArgumentNullException("p");
  Storyboard.SetTarget(t, item);
  Storyboard.SetTargetProperty(t, new PropertyPath(p));
  storyboard.Children.Add(t);
}
Need to be changed to
 public static void AddAnimation(this Storyboard storyboard,
 DependencyObject item, Timeline t, string property)
{
  if (string.IsNullOrWhiteSpace(property)) throw new ArgumentNullException("property");
  Storyboard.SetTarget(t, item);
  Storyboard.SetTargetProperty(t, property);
  storyboard.Children.Add(t);
}
This is bad news, since it breaks the public interface. And it breaks even more, namely another public extension method
public static void AddTranslationAnimation(this Storyboard storyboard,
   FrameworkElement fe, Point from, Point to, Duration duration,
   EasingFunctionBase easingFunction)
{
  storyboard.AddAnimation(
      fe.RenderTransform,
      storyboard.CreateDoubleAnimation(duration, from.X, to.X, easingFunction),
                                       CompositeTransform.TranslateXProperty);
  storyboard.AddAnimation(fe.RenderTransform,
       storyboard.CreateDoubleAnimation(duration, from.Y, to.Y, easingFunction),
                                        CompositeTransform.TranslateYProperty);
}
Needs to be changed to
public static void AddTranslationAnimation(this Storyboard storyboard,
  FrameworkElement fe, Point from, Point to, Duration duration,
  EasingFunctionBase easingFunction)
{
  storyboard.AddAnimation(fe.RenderTransform,
  storyboard.CreateDoubleAnimation(duration, from.X, to.X, easingFunction),
                                   "TranslateX");
  storyboard.AddAnimation(fe.RenderTransform,
  storyboard.CreateDoubleAnimation(duration, from.Y, to.Y, easingFunction),
                                   "TranslateY");
}

I must honestly say I find the apparent need to specify storyboard target properties verbatim, as in strings, quite peculiar, but apparently this is the way it needs to be done. I am only the messenger here.

Porting DragFlickBehavior

Here we go again. Delete
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Wp7nl.Utilities;
And Control-dotting learns you the following needs to be added:
using Win8nl.Utilities;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using WinRtBehaviors;
Soon after that, you'll learn that the second parameter of a ManipulationDelta event is no longer of type ManipulationDeltaEventArgs but of ManipulationDeltaRoutedEventArgs, and that it does no longer have a “DeltaManipulation” property but a plain “Delta” property. So the AssociatedObjectManipulationDelta method capturing the event was this:
void AssociatedObjectManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
  var dx = e.DeltaManipulation.Translation.X;
  var dy = e.DeltaManipulation.Translation.Y;
  var currentPosition = elementToAnimate.GetTranslatePoint();
  elementToAnimate.SetTranslatePoint(currentPosition.X + dx, currentPosition.Y + dy);
}
and now needs to be this
void AssociatedObjectManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
  var dx = e.Delta.Translation.X;
  var dy = e.Delta.Translation.Y;
  var currentPosition = elementToAnimate.GetTranslatePoint();
  elementToAnimate.SetTranslatePoint(currentPosition.X + dx, currentPosition.Y + dy);
}

No rocket science in there, right? And almost identical set of rework needs to be done to the method capturing ManipulationCompleted. Its second parameter was of type ManipulationCompletedEventArgs and is now – you’ve probably guessed it – ManipulationCompletedRoutedEventArgs. And that does no longer have a property e.FinalVelocities.LinearVelocity.X and Y but is does have a Velocities.Linear.X and Y.

For some reason though, those properties return values that are somewhere between 0 and 1, or at least it seems so. So I made a rule-of-thumb conversion multiplying them by 1000. Wrapping that up: AssociatedObjectManipulationCompleted used to be

private void AssociatedObjectManipulationCompleted(object sender,
                                                    ManipulationCompletedEventArgs e)
{
  // Create a storyboard that will emulate a 'flick'
  var currentPosition = elementToAnimate.GetTranslatePoint();
  var velocity = e.FinalVelocities.LinearVelocity;
  var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd };

  var to = new Point(currentPosition.X + (velocity.X / BrakeSpeed),
                     currentPosition.Y + (velocity.Y / BrakeSpeed));
  storyboard.AddTranslationAnimation(elementToAnimate, currentPosition, to, 
    new Duration(TimeSpan.FromMilliseconds(500)), 
    new CubicEase {EasingMode = EasingMode.EaseOut});
  storyboard.Begin();
}
and it now is
private void AssociatedObjectManipulationCompleted(object sender,
                                                   ManipulationCompletedRoutedEventArgs e)
{
  // Create a storyboard that will emulate a 'flick'
  var currentPosition = elementToAnimate.GetTranslatePoint();
  var xVelocity = e.Velocities.Linear.X * 1000;
  var yVelocity = e.Velocities.Linear.Y * 1000;
  var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd };
  var to = new Point(currentPosition.X + (xVelocity / BrakeSpeed),
                     currentPosition.Y + (yVelocity / BrakeSpeed));
  storyboard.AddTranslationAnimation(elementToAnimate, currentPosition, to,
      new Duration(TimeSpan.FromMilliseconds(500)),
   new CubicEase { EasingMode = EasingMode.EaseOut });
  storyboard.Begin();
}

The final thing you will need to take into consideration when you port behaviors is the fact that you used to have an OnAttached and Loaded event. You still have those, but by the very nature I implemented behaviors everything that happened in OnAttached and OnLoaded needs to be in OnAttached. Same goes for Unloaded and OnDetaching – the last one is fired by the first one. So follow the pattern I set out: initialize in OnAttached only, and only clean up in OnDetached.

So, the behavior used to have a setup like this:

protected override void OnAttached()
{
  base.OnAttached();
  AssociatedObject.Loaded += AssociatedObjectLoaded;
  AssociatedObject.ManipulationDelta += AssociatedObjectManipulationDelta;
  AssociatedObject.ManipulationCompleted += AssociatedObjectManipulationCompleted;
}

void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
  elementToAnimate = AssociatedObject.GetElementToAnimate();
  if (!(elementToAnimate.RenderTransform is CompositeTransform))
  {
    elementToAnimate.RenderTransform = new CompositeTransform();
    elementToAnimate.RenderTransformOrigin = new Point(0.5, 0.5);
  }
}
And that should now be 
protected override void OnAttached()
{
  elementToAnimate = AssociatedObject.GetElementToAnimate();
  if (!(elementToAnimate.RenderTransform is CompositeTransform))
  {
    elementToAnimate.RenderTransform = new CompositeTransform();
    elementToAnimate.RenderTransformOrigin = new Point(0.5, 0.5);
  }
  AssociatedObject.ManipulationDelta += AssociatedObjectManipulationDelta;
  AssociatedObject.ManipulationCompleted += AssociatedObjectManipulationCompleted;
  AssociatedObject.ManipulationMode = 
    ManipulationModes.TranslateX | ManipulationModes.TranslateY;
  base.OnAttached();
}
Notice a couple of interesting things:
  • The capture of “Loaded” is gone. We don’t need that any longer
  • There is an extra last line, setting the “ManipulationMode”. Apparently you need to set that up to make ManipulationDelta and ManipulationCompleted happen at all. It accidently stumbled upon that

Finally, the last part: OnDetaching. It used to be

protected override void OnDetaching()
{
  AssociatedObject.Loaded -= AssociatedObjectLoaded;
  AssociatedObject.ManipulationCompleted -= AssociatedObjectManipulationCompleted;
  AssociatedObject.ManipulationDelta -= AssociatedObjectManipulationDelta;

  base.OnDetaching();
}
And the only thing that needs to be changed to use that is the removal of the first line: AssociatedObject.Loaded -= AssociatedObjectLoaded;;

And then we’re done. If you add this behavior to any object on the screen, like I showed in the previous post:

<Page
  x:Class="Catchit8.BlankPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:Catchit8"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:Win8nl_Behaviors="using:Win8nl.Behaviors"
  xmlns:WinRtBehaviors="using:WinRtBehaviors"
  mc:Ignorable="d">

  <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
    <TextBlock HorizontalAlignment="Left" Margin="503,213,0,0" TextWrapping="Wrap" 
   VerticalAlignment="Top" FontSize="18" Text="Drag me">
      <WinRtBehaviors:Interaction.Behaviors>
         <Win8nl_Behaviors:DragFlickBehavior BrakeSpeed ="5"/>
      </WinRtBehaviors:Interaction.Behaviors>
    </TextBlock>
    <Button Content="Drag me too!" HorizontalAlignment="Left" Margin="315,269,0,0" 
   VerticalAlignment="Top" >
      <WinRtBehaviors:Interaction.Behaviors>
          <Win8nl_Behaviors:DragFlickBehavior BrakeSpeed ="5"/>
      </WinRtBehaviors:Interaction.Behaviors>
     </Button>

  </Grid>
</Page>

You will get an effect like this (I added a slider just for kicks)

DragFlickBehavior on Windows 8 demonstrated

Conclusion

At first glance, Windows 8 development does not seem to differ that much from Windows Phone development. After I made my behavior framework, porting a fairly complex behavior like this was pretty easy, so I’d say that holds true at second glance as well. Sure, some things are different – mostly namespaces and some property names. The XAML is a wee bit different as well. As to why Microsoft have decided to change namespaces, rename properties or methods or even let return values be a bit different – I don’t know. What I do know is that bitching about it will probably raise your blood pressure but it won’t help you very much as a developer. Just think of this: Microsoft sold 450 million copies of Windows 7. I don’t think those will all be Windows 8 next year, but I think the 100 million mark will be hit pretty soon. The choice is yours – either you are spending time and energy on getting angry that Microsoft moved your cheese (or actually, only some of it) or you can go out and find new and probably a bloody lot of cheese.

Well, I’ve made my choice

As usual, a complete demo solution for those who, like me, are too lazy to do all the typing themselves, can be found here. So you can get started even faster. ¡Arriba! ¡Andale! ;-)

29 March 2012

Attached behaviors for Windows 8 Metro Style XAML

This post was updated substantially at March 31, 2012

Regular readers of my blog know that there are some recurring themes: MVVM, maps and behaviors. I am a big fan of using behaviors ever since I learned how to use this rooting trough the sources of MVVMLight. When I saw the BUILD videos I was elated. I saw a great merger of the best things of Windows and  Windows Phone 7 styles and I knew I was going to get on board too. Five months later, I found myself being an MVP and on the Microsoft Campus of all places, and got a pretty unpleasant surprise: a lady presenting the new Expression Blend version said there would be no behaviors in Windows 8 Metro Style XAML. I was quite disappointed at the time. I still think it’s is quite an omission, but then again, when it’s not your deadline it’s always easy to criticize others.

And then for some reason, this week, I remembered a single line from a presentation by Laurent Bugnion on the 2012 Microsoft Techdays in The Hague. “You can’t use behaviors but you can use attached dependency properties”. It kept reverbing trough my brain for a few moments.

“Use the Force, Luke” ;-)

And the result is this. It’s crude, it’s clumsy, it has no Blend support, but it works, more or less – I have been able to port my DragFlickBehavior to Windows 8 and it bloody works, too. This blog post will be split in two parts: in this part, I will show how to make a behavior in Windows 8 XAML in general, and in a next one I will specifically show the DragFlickBehavior itself.

First of all, the behavior class itself:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;

namespace WinRtBehaviors
{
  public abstract class Behavior : DependencyObject
  {
    private FrameworkElement associatedObject;
    public FrameworkElement AssociatedObject
    {
      get
      {
        return associatedObject;
      }
      set
      {
        if (associatedObject != null)
        {
          OnDetaching();
        }
        associatedObject = value;
        if (associatedObject != null)
        {
          OnAttached();
        }
      }
    }

    protected virtual void OnAttached()
    {
      AssociatedObject.Unloaded += AssociatedObjectUnloaded;
    }

    protected virtual void OnDetaching()
    {
      AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
    }

    void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
    {
      OnDetaching();
    }
  }
}

This is partially ‘borrowed’ from the Windows Phone System.Windows.Interactivity.dll, courtesy of Reflector.  I don’t have a real ‘detached’ event so I’ve decided to call the “OnDetaching’ method when the FrameworkElement is unloaded. Gotta use what’s available, right? The next class, which is the typed version of AttachedBehavior, is also courtesy of Reflector:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;

namespace WinRtBehaviors
{
  public abstract class Behavior<T> : Behavior 
    where T : FrameworkElement
  {
    protected Behavior()
    {
    }

    public T AssociatedObject
    {
      get
      {
        return (T)base.AssociatedObject;
      }
      set
      {
        base.AssociatedObject = value;
      }
    }
  }
}

I’ve closely followed naming conventions as used in Windows Phone and Silverlight, but I took a different root namespace “WinRtBehaviors”. Should the Windows 8 team decide to add behaviors to the API in the future, removing this classes and changing the namespaces should do the trick

Finally there is this pretty crazy piece of code, which is basically a giant Attached Dependency property. This connects the ‘behaviors’ to the FrameworkElements:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.ApplicationModel;

namespace WinRtBehaviors
{
  /// <summary>
  /// Attached dependency property storing 'behaviors'
  /// </summary>
  public static class Interaction
  {
    public static readonly DependencyProperty BehaviorsProperty =
       DependencyProperty.RegisterAttached("Behaviors",
       typeof(ObservableCollection<Behavior>),
       typeof(Interaction),
       new PropertyMetadata(
         DesignMode.DesignModeEnabled ? new ObservableCollection<Behavior>() : null,         
       BehaviorsChanged));


    /// <summary>
    /// Called when Property is retrieved
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static ObservableCollection<Behavior> GetBehaviors(DependencyObject obj)
    {
      var associatedObject = obj as FrameworkElement;
      var behaviors = obj.GetValue(BehaviorsProperty) as ObservableCollection<Behavior>;
      if (behaviors == null)
      {
        behaviors = new ObservableCollection<Behavior>();
        SetBehaviors(obj, behaviors);
      }

      return behaviors;
    }

    /// <summary>
    /// Called when Property is retrieved
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="value"></param>
    public static void SetBehaviors(
       DependencyObject obj,
       ObservableCollection<Behavior> value)
    {
      obj.SetValue(BehaviorsProperty, value);
    }

    /// <summary>
    /// Called when the property changes
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private static void BehaviorsChanged(
     object sender,
     DependencyPropertyChangedEventArgs args)
    {
      var associatedObject = sender as FrameworkElement;
      if (associatedObject != null)
      {
        var oldList = args.OldValue as ObservableCollection<Behavior>;
        if (oldList != null)
        {
          foreach (var behavior in oldList)
          {
            behavior.AssociatedObject = null;
          }
        }

        var newList = args.NewValue as ObservableCollection<Behavior>;
        if (newList != null)
        {
          foreach (var behavior in newList)
          {
            behavior.AssociatedObject = sender as FrameworkElement;
          }
          newList.CollectionChanged += (collectionSender, collectionArgs) =>
          {
            switch (collectionArgs.Action)
            {
              case NotifyCollectionChangedAction.Add:
                {
                  foreach (Behavior behavior in collectionArgs.NewItems)
                  {
                    behavior.AssociatedObject = associatedObject;
                  }
                  break;
                }
              case NotifyCollectionChangedAction.Reset:
              case NotifyCollectionChangedAction.Remove:
                {
                  foreach (Behavior behavior in collectionArgs.NewItems)
                  {
                    behavior.AssociatedObject = null;
                  }
                  break;
                }
            }
          };
        }
      }
    }
  }
}

So what do we have here? On top, a pretty standard way of registering an attached dependency property – an ObservableCollection of Behavior. Notice the fact the initial value is null in runtime, but an empty collection in design time. This is because of the next part, the mandatory GetBehaviors method. This is normally ‘just a getter’, but it checks if the collection is null first. And then something interesting happens:

  • If it is null, it creates a new empty collection and initializes the attached dependency property itself with it.
  • That, in turn, fires BehaviorsChanged
  • BehaviorsChanged attaches an internal anonymous method to the ObservableCollectionChanged event of the behavior collection.
  • That anonymous method basically rams the FrameworkElement to which this ObservableCollection is attached in the AssociatedObject property of every new behavior that’s added the list.
  • This will fire the overrideable OnAttached method in the bavhior and boom – your behavior is ready to go.

The SetBehaviors method then is pretty standard. The basic pattern of a behavior is then something like this:

namespace Win8nl.Behaviors
{
  public class DragFlickBehavior : AttachedBehavior<FrameworkElement>
  {
    protected override void OnAttached()
    {
      // Do something
      base.OnAttached();
    }
    protected override void OnDetaching()
    {
      // Do something
      base.OnDetaching();
    }
  }
}

Which, not entirely by accident, looks quite a lot like an behavior looks in Windows Phone or Silverlight. And you call it in XAML like this:

<Page
  x:Class="Catchit8.BlankPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:Catchit8"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:Win8nl_Behaviors="using:Win8nl.Behaviors"
  xmlns:WinRtBehaviors="using:WinRtBehaviors"
  mc:Ignorable="d">

  <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
    <TextBlock HorizontalAlignment="Left" Margin="503,213,0,0" TextWrapping="Wrap" 
   VerticalAlignment="Top" FontSize="18" Text="Drag me">
      <WinRtBehaviors:Interaction.Behaviors>
         <Win8nl_Behaviors:DragFlickBehavior BrakeSpeed ="5"/>
      </WinRtBehaviors:Interaction.Behaviors>
    </TextBlock>
    <Button Content="Drag me too!" HorizontalAlignment="Left" Margin="315,269,0,0" 
   VerticalAlignment="Top" >
      <WinRtBehaviors:Interaction.Behaviors>
          <Win8nl_Behaviors:DragFlickBehavior BrakeSpeed ="5"/>
      </WinRtBehaviors:Interaction.Behaviors>
     </Button>

  </Grid>
</Page>

Like I said: crude, clumsy and no Blend support. There are a few issues with it. The anonymous method doing all the work, is never detached. I wonder how much memory leaks this will produce. But at least I can move forward now porting a lot of stuff I made for Windows Phone to Windows 8. Unfortunately, contrary to what I hoped, data binding to dependency properties of the behavior itself does not seem to work yet (thanks to Filip Skakun for pointing that out in a reaction to that this post) [It does now, see below]. I hope people smarter than me can improve this to a possible better solution. I will soon post a demo solution with the DragFlickBehavior in working condition in it, after I have traced back how I got it working it the first place.

In the mean time, I’ve started an CodePlex project that will be the home of this stuff. I was initially planning of including it in my Win8nl CodePlex library (coming soon) but after careful consideration and advice, I decided to make a separate library. After all, if people would like to go ahead and expand this, adding triggers and whatnot, it would probably interfere with my own ‘hobby’ library and vice versa.

So Metro, meet behaviors, at “WinRtBehaviors”.

Very much thanks to Geert van Horrik for the suggestions that led to the improvement of my first version.  He is as of now registered as a developer on WinRtBehaviors.

Update 04-04-2012 The WinRtBehaviors library is adapted: a) it no longer introduces memory leaks as described, (thanks to Geert van Horrik), and b) Filip Skakun provided me with code to actually enable data binding.