Click here to Skip to main content
Licence CPOL
First Posted 24 Feb 2012
Views 8,082
Bookmarked 5 times

Nice panning and zooming in Windows Phone 7

By | 24 Feb 2012 | Technical Blog
How to make zoom and panning properly in Windows Phone 7
A Technical Blog article. View original blog here.[^]

A little time ago, I published an app called Hidden Pics on the Microsoft Marketplace. In that entry, I covered some basics on the Isolated Storage and the PhotoChooserTask for Windows Phone 7. In this entry, I want to cover the zooming and panning. As part of that app, the user has the option to open a photo full size on the device screen and then pinch to zoom the photo. They can also pan by dragging on the screen with the fingers.

Now this is supposed to be very basic, but there is a catch, which is making the zoom stable. By stable, I mean that if you use two fingers to zoom in the photo, once you finish, the two fingers should have the same pixels behind them, which indicates that you zoomed proportionally and panned correctly. After Googling around a bit, I put together the code to do both zooming and panning properly.

So here is the XAML for the control, I am using an Image control inside a Canvas with a CompositeTransform:

 <Image Width="480"  Height="720"    x:Name="MyImage" Stretch="Uniform" >
            <toolkit:GestureService.GestureListener>
                <toolkit:GestureListener Flick="GestureListener_Flick"
                PinchStarted="OnPinchStarted" DragDelta="GestureListener_DragDelta"
                PinchDelta="OnPinchDelta"/>
            </toolkit:GestureService.GestureListener>
            <Image.RenderTransform>
                <CompositeTransform x:Name="myTransform"
                ScaleX="1" ScaleY="1"
                TranslateX="0" TranslateY="0"/>
            </Image.RenderTransform>
        </Image> 

The photo will start fully fitted on the Image control, and in this case my app starts in portrait. Now the event is hooked for flicks (I use them to switch from one photo to the next one) and the PinchDelta and PinchStarted events are the ones we will use here. Now check the code and see how it is done.

private double _imageScale = 1d;
private Point _imageTranslation = new Point(0, 0);
private Point _fingerOne;
private Point _fingerTwo;
private double _previousScale;

private void OnPinchStarted(object s, PinchStartedGestureEventArgs e)
{
    _fingerOne = e.GetPosition(MyImage, 0);
    _fingerTwo = e.GetPosition(MyImage, 1);
    _previousScale = 1;
}

private void OnPinchDelta(object s, PinchGestureEventArgs e)
{
    var newScale = e.DistanceRatio/_previousScale;
    var currentFingerOne = e.GetPosition(MyImage, 0);
    var currentFingerTwo = e.GetPosition(MyImage, 1);
    var translationDelta = GetTranslationOffset(currentFingerOne, 
    currentFingerTwo, _fingerOne, _fingerTwo, _imageTranslation, newScale);
    _fingerOne = currentFingerOne;
    _fingerTwo = currentFingerTwo;
    _previousScale = e.DistanceRatio;
    UpdatePicture(newScale, translationDelta);
}

private void UpdatePicture(double scaleFactor, Point delta)
{
    var newscale = _imageScale*scaleFactor;
    var transform = (CompositeTransform) MyImage.RenderTransform;
    if (newscale > 1)
    {
        ApplicationBar.IsVisible = false;
        _imageScale *= scaleFactor;
        _imageTranslation = new Point
        (_imageTranslation.X + delta.X, _imageTranslation.Y + delta.Y);
        transform.ScaleX = _imageScale;
        transform.ScaleY = _imageScale;
        transform.TranslateX = _imageTranslation.X;
        transform.TranslateY = _imageTranslation.Y;
    }
    else
    {
        ApplicationBar.IsVisible = true;
        transform.TranslateX = 0;
        transform.TranslateY = 0;
        transform.ScaleX = transform.ScaleY = 1;
        _imageTranslation = new Point(0, 0);
    }
}

private Point GetTranslationOffset(Point currentFingerOne, Point currentFingerTwo, 
Point oldFingerOne, Point oldFingerTwo, Point currentPosition, double scale)
{
    var newFingerOnePosition = new Point(
        currentFingerOne.X + (currentPosition.X - oldFingerOne.X)*scale,
        currentFingerOne.Y + (currentPosition.Y - oldFingerOne.Y)*scale);
    var newFingerTwoPosition = new Point(
        currentFingerTwo.X + (currentPosition.X - oldFingerTwo.X)*scale,
        currentFingerTwo.Y + (currentPosition.Y - oldFingerTwo.Y)*scale);
    var newPosition = new Point(
        (newFingerOnePosition.X + newFingerTwoPosition.X)/2,
        (newFingerOnePosition.Y + newFingerTwoPosition.Y)/2);
    return new Point(
        newPosition.X - currentPosition.X,
        newPosition.Y - currentPosition.Y);
}

private void PhoneApplicationPage_OrientationChanged
    (object sender, OrientationChangedEventArgs e)
{
    if (e.Orientation == PageOrientation.Landscape || 
        e.Orientation == PageOrientation.LandscapeLeft ||
        e.Orientation == PageOrientation.LandscapeRight)
    {
        MyImage.Width = 720;
        MyImage.Height = 480;
    }
    else
    {
        MyImage.Width = 480;
        MyImage.Height = 720;
    }
} 

Now notice the OrientationChanged event of the Silverlight page is here, the reason is that I want the user to be able to use the zoom and panning either in portrait or landscape mode, but for that, I do really need Width and Height of the Image control to be set. Also I check if the scale is 1 or less, then in that case the ApplicationBar is shown and the image is fit to the screen. So if the photo is not zoomed, a flick will move to the next or previous photo, but then the photo is zoomed, no menu and flicking will just pan around the photo. See the code for the flicking event below. 

private void GestureListener_Flick(object sender, FlickGestureEventArgs e)
      {
          if (ApplicationBar.IsVisible && e.Direction == System.Windows.Controls.Orientation.Horizontal)
          {
              if (e.HorizontalVelocity < 0) toLeft.Begin();
              else toRight.Begin();
          }
      } 
Hope it Helps...

History

  • May 15, 2012: Added flick event code 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Leonardo Paneque

Team Leader

United States United States

Member

Follow on Twitter Follow on Twitter
Leonardo loves to code with C# and thinks .NET platforms rocks.
He has a Master degree in Computer Sciences and likes to share code and ideas.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionFlicking PinmemberScott Spradlin6:45 31 Mar '12  
QuestionGood article Pinmemberdbaechtel15:15 29 Feb '12  
AnswerRe: Good article PinmemberLeonardo Paneque15:33 29 Feb '12  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 24 Feb 2012
Article Copyright 2012 by Leonardo Paneque
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid