Windows 7: Experimenting with Multi-Touch on Windows 7 ( Part 5 )

I got around to installing Visual Studio 2010 Beta 1 and so I thought it made sense to follow up this post by taking a quick look at what WPF V4.0 Beta 1 has to offer in the same area.

As far as I know, Beta 1 does not have the low level WM_TOUCH style message processing that I looked at a little here and here but it does have support for gestures and inertia ( which is probably the support that most applications want/need ) similar to what I used from the wrapper library in the posts here and here.

I defined another UI to play with which just presents a rectangle on a canvas;

<Window x:Class="WpfApplication9.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300"
        WindowState="Maximized">
    <Grid>
        <Canvas 
            x:Name="canvas"
            Background="Blue" 
            ManipulationMode="All"
            ManipulationDelta="OnManipulationDelta"
            ManipulationInertiaStarting="OnInertiaStarting">
            <Rectangle Canvas.Left="20" 
                       Canvas.Top="20" 
                       Height="60" 
                       Name="rectangle1" 
                       Stroke="Black" 
                       Width="145" 
                       Fill="#FFDFFF00"
                       RenderTransformOrigin="0.5,0.5">
                <Rectangle.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform x:Name="scale"/>
                        <RotateTransform  x:Name="rotate"/>
                        <TranslateTransform x:Name="translate"/>
                    </TransformGroup>
                </Rectangle.RenderTransform>
            </Rectangle>
        </Canvas>
    </Grid>
</Window>

Notice that in WPF we get ManipulationMode to set to a bitmask so that we can pick the usual gestures like Rotate/Scale/Translate or All as I’ve done here. We also get ManipulationDelta and ManipulationInertiaStarting events and so WPF is bringing these events and properties right into its UI framework which makes it very easy to program against. I threw a little code behind it;

  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
    }
    private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
    {
      Manipulation manip = e.GetDeltaManipulation(canvas);

      translate.X += manip.Translation.X;
      translate.Y += manip.Translation.Y;

      rotate.Angle += manip.Rotation;

      scale.ScaleX *= manip.Scale;
      scale.ScaleY *= manip.Scale;

      // Trying to get velocities here. The docs (win7 SDK) suggest
      // grabbing velocities when the manipulation complete but
      // the actual event for that fires after the intertia finishes
      // the manipulation "for you" rather than before which is when
      // you need the velocities.
      ManipulationVelocities velocities = e.GetVelocities(canvas);

      if (velocities.AngularVelocity > 0.0)
      {
        angularVelocity = velocities.AngularVelocity;
      }
      if (velocities.LinearVelocity.Length > 0.0)
      {
        linearVelocity = new Vector(
          velocities.LinearVelocity.X / 20.0, velocities.LinearVelocity.Y / 20.0);
      }
    }
    private void OnInertiaStarting(object sender, 
      ManipulationInertiaStartingEventArgs e)
    {
      // Not doing anything with expansion here...
      e.AddInertiaParameters(new DesiredDecelerations(canvas)
      {
        AngularDeceleration = 0.00001f,
        LinearDeceleration = 0.0005f
      });

      e.SetInitialVelocities(canvas, new ManipulationVelocities(
        linearVelocity, angularVelocity, 0));
    }
    double angularVelocity;
    Vector linearVelocity;
  }

Note – this is really just a starting point in that there are a couple of things in that fragment that aren’t likely to be quite right;

  1. Not quite sure whether we should be passing the Canvas or the Rectangle as the UIElement that we capture velocities for in the call to GetVelocities()
  2. The values provided for initial velocities and for decelerations are still pretty much “fudged”. It’s not exactly clear to me how these velocities match up with those that come back from GetVelocities and quite how you’re meant to plug the inertia processor into the manipulation processor.

With those caveats – the programming model here is pretty simple in that it’s just surfaced from WPF in a natural way that makes it pretty easy to do the manipulations and plug them together with inertia even if I am still struggling to figure out exactly how to pass parameters from one to the other and what values do/don’t make sense for those deceleration properties.

Update 1…

I got a bit more information. Anson on the WPF team kindly dropped me a mail to explain what some of these values mean in WPF Beta 1 – note that this may not be the same for subsequent previews but it’s where things stand today. Specifically he gave me the detail as to how the velocities and decelerations are specified;

Velocities

    – Translation: DIPs (device independent pixels: 1/96 inch) per millisecond

    – Rotation: degrees per millisecond

    – Expansion: DIPs per millisecond

Decelerations

    – Translation: DIPs per millisecond squared

    – Rotation: degrees per millisecond squared

    – Expansion: DIPS per millisecond squared

     

and he also pointed me to the presence of GetInitialVelocities on the ManipulationInertiaStartingEvent which makes for an easier life and left my code looking like this ( same XAML definition for the “UI” above );

  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
    }
    private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
    {
      Manipulation manip = e.GetDeltaManipulation(canvas);

      translate.X += manip.Translation.X;
      translate.Y += manip.Translation.Y;

      rotate.Angle += manip.Rotation;

      scale.ScaleX *= manip.Scale;
      scale.ScaleY *= manip.Scale;
    }
    private void OnInertiaStarting(object sender, 
      ManipulationInertiaStartingEventArgs e)
    {
      // Velocities here are measured as;
      // LinearVelocity = (logical) Pixels per millisecond
      // Rotation = Degrees per millisecond
      // Expansion = (local) Pixels per millisecond
      ManipulationVelocities initialVelocities = e.GetInitialVelocities(canvas);      

      // Could then use this call to alter those velocities but (AFAICT) there's
      // no need for me to explicitly do that if I'm just going to continue
      // the initial velocities so commented out.
      //
      // e.SetInitialVelocities(canvas, initialVelocities); 

      // Decelerations here are measured as;
      // LinearDeceleration = (logical) Pixels per millisecond squared
      // Rotation = Degrees per millisecond squared
      // Expansion = (logical) Pixels per millisecond squared
      //
      // Remembering [v = u + at] or [a = ( v - u ) / t] so if we want
      // to slow these things down to 0 over 250ms then we need to do
      // Deceleration = (velocity)/250

      // We could also add a parameter for DesiredTotalManipulation maybe
      // adding 10% or something to the manipulation carried out so far
      // but I've not done that here.
      e.AddInertiaParameters(new DesiredDecelerations(canvas)
      {
        LinearDeceleration = Math.Abs(initialVelocities.LinearVelocity.Length / 250),
        AngularDeceleration = Math.Abs(initialVelocities.AngularVelocity / 250),
        ExpansionDeceleration = Math.Abs(initialVelocities.ExpansionVelocity / 250)
      });            
    }
  }