Silverlight 3 / Expression 3 behaviors for enabling Multi-touch gestures

In the previous post, I’ve described a basic example of WPF custom control for reusing multi-touch functionalities across applications. The same task can be achieved in Silverlight 3 using the new touch apis (check out this great article by Tim Heuer to get started), in addition we can use the new behaviors in order to write and simply reuse the code (a quick note: at this stage Silverlight 3 only supports basic touch events, gesture messages are not available).

To start I’ve added two simple behaviors named TouchDrag and TouchZoom to the Multi-Touch Codeplex project.

These new objects can be used in xaml by inserting the following code (the System.Windows.Interactivity namespace can be imported from the Expression Blend 3 SDK, already available if Blend 3 is installed):

<UserControl x:Class="SilverlightMultiTouch.MainPage"
    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"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480" Width="1280" Height="800"
    xmlns:myControls="clr-namespace:MultiTouch.Controls.Silverlight; assembly=MultiTouch.Controls.Silverlight"
    xmlns:behaviors="clr-namespace: MultiTouch.Controls.Silverlight.Behaviors; assembly=MultiTouch.Controls.Silverlight"
    xmlns:interactivity="clr-namespace: System.Windows.Interactivity; assembly=System.Windows.Interactivity"
             >
    <Canvas Background="Black">
        <!-- Example using Behaviors -->
        <Canvas>
            <interactivity:Interaction.Behaviors>
                <behaviors:TouchDrag TouchDragEnabled="True"/>
                <behaviors:TouchZoom TouchZoomEnabled="True"/>
            </interactivity:Interaction.Behaviors>
            <Image Source="Images/image.png" Height="597" Width="448" Canvas.Left="404" Canvas.Top="96"/>
        </Canvas>
    </Canvas>
</UserControl>

Have you seen the previous lines of code? The touch features are simply associated to the Image object using this xaml:

            <interactivity:Interaction.Behaviors>
                <behaviors:TouchDrag TouchDragEnabled="True"/>
                <behaviors:TouchZoom TouchZoomEnabled="True"/>
            </interactivity:Interaction.Behaviors>

The TouchDrag and TouchZoom behaviors can be defined in code-behind in this way:

TouchDrag.cs

    public class TouchDrag : Behavior<FrameworkElement>
    {
        //Dependency property for enabling the TouchDrag
        public static readonly DependencyProperty TouchDragEnabledProperty =
            DependencyProperty.Register("TouchDragEnabled", typeof(bool), typeof(TouchDrag), new PropertyMetadata(false));

        [Category("Target Properties")]
        public bool TouchDragEnabled { get; set; }

        //Occurs when the primary point is touched down
        private bool _touchDown = false;

        //Origin point
        private Point _ptOrigin;

        //Translate Transform
        TranslateTransform _translateTransform;

        /// Initialize the behavior
        protected override void OnAttached()
        {
            base.OnAttached();
            Touch.FrameReported += AssociatedObjectTouchFrameReported;
        }

        /// Occurs when detaching the behavior
        protected override void OnDetaching()
        {
            base.OnDetaching();
            Touch.FrameReported -= AssociatedObjectTouchFrameReported;
        }

        /// Touch Frame Handler
        private void AssociatedObjectTouchFrameReported(object sender, TouchFrameEventArgs e)
        {
            //Handler for the Touch events
            if (this.TouchDragEnabled)
            {
                if (e.GetPrimaryTouchPoint(AssociatedObject) != null)
                {
                    switch (e.GetPrimaryTouchPoint( AssociatedObject).Action)
                    {
                        case TouchAction.Up:
                            this.AssociatedObjectTouchUp(sender, e);
                            break;
                        case TouchAction.Move:
                            this.AssociatedObjectTouchMove(sender, e);
                            break;
                        case TouchAction.Down:
                            this.AssociatedObjectTouchDown(sender, e);
                            break;
                    }
                }
            }
        }

        /// Touch Down Handler
        private void AssociatedObjectTouchDown(object sender, TouchFrameEventArgs e)
        {
            this._touchDown = true;
            this.AssociatedObject.Opacity = .75;
            this._ptOrigin = e.GetPrimaryTouchPoint(AssociatedObject).Position;

            //Initialize the Translate transform and the TransformGroup
            if (this._translateTransform == null)
            {
                this._translateTransform = new TranslateTransform();
                if ((this.AssociatedObject.RenderTransform == null) || !(this.AssociatedObject.RenderTransform is TransformGroup))
                    this.AssociatedObject.RenderTransform = new TransformGroup();
                if (this.AssociatedObject.RenderTransform is TransformGroup)
                    (this.AssociatedObject.RenderTransform as TransformGroup).Children.Add( this._translateTransform);
            }

            //Suspend Mouse promotion
            e.SuspendMousePromotionUntilTouchUp();
        }

        /// Handle the Touch movement
        private void AssociatedObjectTouchMove(object sender, TouchFrameEventArgs e)
        {
            if (this._touchDown)
            {
                //Find the new position
                Point newPosition = e.GetPrimaryTouchPoint(AssociatedObject.Parent as FrameworkElement).Position;

                //Drag the Control using the TranslateTransform
                this._translateTransform.X = (newPosition.X - _ptOrigin.X); this._translateTransform.Y = (newPosition.Y - _ptOrigin.Y);
            }
        }

        /// Touch Up Handler
        private void AssociatedObjectTouchUp(object sender, TouchFrameEventArgs e)
        {
            // turn off drag
            this._touchDown = false;
            AssociatedObject.Opacity = 1;
        }
    }

TouchZoom.cs:

    public class TouchZoom : Behavior<FrameworkElement>
    {
        //Dependency property for enabling the TouchZoom
        public static readonly DependencyProperty TouchDragEnabledProperty =
            DependencyProperty.Register("TouchZoomEnabled", typeof(bool), typeof(TouchZoom), new PropertyMetadata(false));

        [Category("Target Properties")]
        public bool TouchZoomEnabled { get; set; }

        //Occurs when the primary point is touched down
        private bool _touchDown = false;

        //Origin points
        private TouchPointCollection _tpInitialPoints;

        //ScaleTransform
        ScaleTransform _scaleTransform;

        /// Calculate the distance between two points
        private double Distance(Point point1, Point point2)
        {
            try
            {
                return Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) + (point1.Y - point2.Y) * (point1.Y - point2.Y));
            }
            catch { return double.NaN; }
        }

        /// Calculate the MidPoint
        private Point MidPoint(Point point1, Point point2)
        {
            return new Point((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2);
        }

        /// Initialize the behavior
        protected override void OnAttached()
        {
            base.OnAttached();
            Touch.FrameReported += AssociatedObjectTouchFrameReported;
        }

        /// Occurs when detaching the behavior
        protected override void OnDetaching()
        {
            base.OnDetaching();
            Touch.FrameReported -= AssociatedObjectTouchFrameReported;
        }

        /// Touch Frame Handler
        private void AssociatedObjectTouchFrameReported(object sender, TouchFrameEventArgs e)
        {
            if (this.TouchZoomEnabled)
            {
                TouchPointCollection tpCollection = e.GetTouchPoints(this.AssociatedObject);
                tpCollection.ToList().ForEach(tp =>
                    {
                        switch (tp.Action)
                        {
                            case TouchAction.Up:
                                this.AssociatedObjectTouchUp(sender, e);
                                break;
                            case TouchAction.Move:
                                this.AssociatedObjectTouchMove(sender, e);
                                break;
                            case TouchAction.Down:
                                this.AssociatedObjectTouchDown(sender, e);
                                break;
                        }
                    });
            }
        }

        /// Touch Down Handler
        private void AssociatedObjectTouchDown(object sender, TouchFrameEventArgs e)
        {
            //Initialize the parameters
            this._touchDown = true;
            this.AssociatedObject.Opacity = .75;
            this._tpInitialPoints = e.GetTouchPoints(this.AssociatedObject);

            //Initialize the Scale transform
            if (this._scaleTransform == null)
            {
                this._scaleTransform = new ScaleTransform();
                if ((this.AssociatedObject.RenderTransform == null) || !(this.AssociatedObject.RenderTransform is TransformGroup))
                    this.AssociatedObject.RenderTransform = new TransformGroup();
                if (this.AssociatedObject.RenderTransform is TransformGroup)
                    (this.AssociatedObject.RenderTransform as TransformGroup).Children.Add( this._scaleTransform);
            }
            //TODO: e.SuspendMousePromotionUntilTouchUp();
        }

        /// Handle the Touch movement
        private void AssociatedObjectTouchMove(object sender, TouchFrameEventArgs e)
        {
            if (this._touchDown)
            {
                //Find the new points
                TouchPointCollection newPoints = e.GetTouchPoints(AssociatedObject.Parent as FrameworkElement);
                if (newPoints!=null && this._tpInitialPoints!=null)
                    if (this._tpInitialPoints.Count == 2 && newPoints.Count == 2)
                    {
                        //Zoom the Control using the ScaleTransform
                        double ratio = this.Distance( newPoints.First().Position, newPoints.Last().Position) /
                            this.Distance( this._tpInitialPoints.First().Position, this._tpInitialPoints.Last().Position);
                        if (ratio != double.NaN)
                        {
                            Point midPoint = this.MidPoint( newPoints.First().Position, newPoints.Last().Position);
                            this._scaleTransform.CenterX = midPoint.X; this._scaleTransform.CenterY = midPoint.Y;
                            this._scaleTransform.ScaleX = ratio; this._scaleTransform.ScaleY = ratio;
                        }
                    }
            }
        }

        /// Touch Up Handler
        private void AssociatedObjectTouchUp(object sender, TouchFrameEventArgs e)
        {
           // turn off Zoom mode
           this._touchDown = false;
           this.AssociatedObject.Opacity = 1;
        }
    }

The behavior approach is very powerful, in this way you can share functionalities between Visual Studio and Expression Blend 3 in a simple way, making the code maintainable and simply reusable in different projects.

The source code is available for download on http://multitouch.codeplex.com.

If you have a multi-touch enabled devices, the demo application is available on-line here, featuring Drag and Zoom gestures.

Hope this helps and… happy Silverlighting!

8 thoughts on “Silverlight 3 / Expression 3 behaviors for enabling Multi-touch gestures

  1. Pingback: A WPF custom control for enabling Windows 7 Multi-touch gestures | DavideZordan.net

  2. Pingback: Futile » links for 2009-08-19

  3. Pingback: NDepend: a great tool to analyze Silverlight and Expression assemblies and save precious time | DavideZordan.net

  4. Pingback: Silverlight 3 Multi-Touch Drag and Zoom gestures published on Expression gallery | DavideZordan.net

  5. These behaviours came a bit too late, I have already written my own been banging my head on if at all its possible to do HitTesting. I guess hit testing is more imporatant and relative than anything else right now.

    Would love to see an Multitouch Application where it have more than one item and allows to interact with independent elements.

  6. Hi Imran,
    agree with you, I’m waiting for the possibility to interact with more elements and then indipendently capture the touch events for each UIEement!

  7. Hay ho, does the WPF sample only run on Win7 because I get an exception telling the following:

    _comManipulationProcessor = new ManipulationInterop.ManipulationProcessor();
    <—
    Retrieving the COM class factory for component with CLSID {597D4FB0-47FD-4AFF-89B9-C6CFAE8CF08E} failed due to the following error: 80040154

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>