Pages

Sunday, July 22, 2012

PanView - A Metro Panning Custom Control

PanView is a custom control that allows you to pan a canvas or any other content from within your MainWindow or from within any other user interface element.

Posts in this series:

A user interface element that supports panning should allow a user to swipe the content left/right with one finger, zoom in/out using two fingers, and rotate using two fingers.

Examples

pan1

Move left/right up/down.

pan2

Rotate clockwise/counterclockwise.

pan3

Zoom in/out.

pan4

See Touch interaction design (Metro style apps) and Guidelines for Panning (Metro style apps) for more information.

My goal is to make adding panning to a C# Metro application as easy as possible. To that end, I created a custom control called PanView.

Install PanView from NuGet.org and add to your existing Metro application:

  1. Install the NuGet Package Manager if you have not already.
  2. Open your Metro application in Visual Studio
  3. Select Manage NuGet Packages from the Project menu.
  4. Click Online. Search for PanView. Click Install.

Edit MainPage.xaml to:

  1. Add the using:PanViewLibrary.
  2. Add the PanView custom control.
  3. Add content to the PanView.  In the example below, the content is a TextBlock.
Example MainPage.xaml:
<Page
   x:Class="PanViewDemoApp.MainPage"
   IsTabStop="false"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="using:PanViewDemoApp"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:pvl="using:PanViewLibrary"
   mc:Ignorable="d">
    <Grid
       Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <pvl:PanView>
            <TextBlock
               Text="This is the pannable content" />
        </pvl:PanView>
    </Grid>
</Page>

Limiting Manipulation Modes

You may choose to limit the types of manipulations the user may perform by setting the PanView dependency property “ManipulationMode”.  The example below limits the panning to simple x/y translations with inertia.

MainPage.xaml
  1.         <pvl:PanView
  2.             ManipulationMode="TranslateX TranslateY TranslateInertia"

Reset

You may programmatically reset the transformations by calling PanView.Reset().  In the XAML and C# code below, clicking the the reset button resets the panning.

MainPage.xaml
    <Grid
        Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <pvl:PanView
            x:Name="MyPanView">
            <Grid
                x:Name="_grid"
                Width="2000"
                Height="2000" />
        </pvl:PanView>
        <Button
            Content="Reset"
            Click="OnResetClicked" />
    </Grid>
MainPage.xaml.cs
        private void OnResetClicked(object sender, RoutedEventArgs e)
        {
            MyPanView.Reset();
        }

Constraining Translations

To limit the amount of translation distance, set the dependency properties MinTranslateX, MaxTranslateX, MinTranslateY, and MaxTranslateY as shown here:

MainPage.xaml
        <pvl:PanView
            MinTranslateX="-200"
            MaxTranslateX="100"
            MinTranslateY="-300"
            MaxTranslateY="600"

Binaries and Source

PanView binaries reside here: http://nuget.org/packages/panview/

PanView source resides here: http://panview.codeplex.com/

23 comments:

  1. Nice control, good job!

    I'm looking into using a control like this but i would like to design some boundaries for the user so they can't move the complete working surface off the screen. Is it possible to add maximum boundaries for left/right and min/max zoom?

    ReplyDelete
    Replies
    1. That certainly can be done. The source code is up at panview.codeplex.com. If you want to contribute to it, feel free. Either way, I will try to add that feature in the next week or so - depending on my schedule.

      I also plan on enhancing panview to support the retrieval of data to render as the user pans. This would be similar to online maps that pull data as the user explores.

      Maybe you can become non-anonymous or just leave your name so I know who you are.

      Delete
    2. Anonymous,

      I added the ability to limit the amount of translations with MinTranslateX, MaxTranslateX, MinTranslateY, and MaxTranslateY. See the updated blogpost, source at codeplex, and binaries at nugget.

      - John

      Delete
  2. Hi John:

    I'm new in Visual Studio programming and I'm doing my first projects. I found the panView very cool. I managed to draw several polylines to create a picture and I was able to scale, move and rotate the image. I'd like to do a couple of things but couldn't figure out how.

    First: I would like to reset the transformations to its original state after operating the panView but could not figure out how to do it.

    Second: I don't want the user to be able to rotate the panView. How can I prevent it from happening?

    Thank...

    Pablo Santa Ana (Argentina)

    ReplyDelete
    Replies
    1. Pablo Santa Ana,

      I added the ability to reset the PanView. See the updates I made to the blogpost. I updated NuGet and CodePlex too.

      I updated the blogpost to explain how to prevent rotations. There was no additional change needed in PanView to support this.

      I also added the ability to limit the amount of translations with MinTranslateX, MaxTranslateX, MinTranslateY, and MaxTranslateY.

      - John

      Delete
  3. Hi John:

    Thank you very much but... (there's always a "but"): in the blog, under "Limiting Manipulation Modes" says " The example below limits the panning to simple x/y translations with inertia." but the code is not shown. A bug, may be?

    Regards...

    Pablo.

    ReplyDelete
    Replies
    1. Pablo,
      There is no C# code required. You just need to set the ManipulationMode in the XAML. Maybe you looked at the blog post while I was in the middle of editing it? The blog post should contain the XAML snippet:

      <pvl:PanView
      ManipulationMode="TranslateX TranslateY TranslateInertia"

      Delete
  4. Hello John,

    Nice work on the Panview. a really useful control i could use in my apps in the future.

    Maybe it's a good idea to make the transform group also settable so you can set the transformation during startup? for example zoom to a specific level at the start?

    regards,

    Geert (twitter @geertvdc)

    ReplyDelete
    Replies
    1. Geert,
      I updated codeplex and nuget with a Reset(CompositeTransform). Does this work for you?

      Delete
    2. Perfect!

      thanks John, I'll be working on an app this weekend using this control.

      If it's not to much work maybe it's smart to also implement mouse zoom on the panview so it's also usable on pc's :)

      Geert

      Delete
    3. oh also.. i think most people use or should use MVVM so instead of a method to (re)set the zoom/pan level it might be better to have a property we can have two way binding on.

      Geert

      Delete
    4. Geert, I moved CurrentTransformation and PreviousTransformations from private fields to public dependency properties for you. I also moved some code from ManipulationStarted to ManipulationCompleted to make accessing PreviousTransformations more logical to a ViewModel.

      Delete
  5. Hello John!

    I happily use this control in my Metro Style project.

    However I run into a quiet simple problem. Because Win 8 can suspend my application anytime it wants, I would like to save the panView's state as recommended. I have tried to serialize currentTransformation and previousTransformation.Matrix and load it back when windows restart my app. But it doesn't give back the same view.

    Could you make it possible to serialize(a method with string return value?) it's internal state and then load it back?

    Thanks
    Adam Gavronek

    ReplyDelete
    Replies
    1. Adam,
      All you need to serialize is the contents of PreviousTransformations.Matrix.
      I hope this helps.

      Delete
  6. Hi John

    Thank you for writing & sharing this control.

    I'm trying to use this and I'm encountering some problems. What I want to do is be able to display a JPEG that could be larger than the size of the screen, hence the need to zoom in and then pan around.

    The XAML I have at the moment is:







    My first observation is that the Stretch parameter doesn't seem to be making any difference to the scaling of the image. Presumably that is a function of PanView as, by default, the image is being shown at full size.

    Second, dragging the image around reveals that the image is getting cropped somewhere/somehow. In other words, if the image is larger than the screen, it starts out showing as much of the image as it can but if I then try to pan, I just end up showing the background.

    Pinch/zoom doesn't seem to be doing anything - the image zoom factor isn't changing.

    Finally, I'd like the initial starting zoom factor to be such that the image is scaled to fit the screen. Is that something your control can do or do I need to calculate it and provide that through the composite transform?

    Many thanks for any insights you can provide.

    Philip

    ReplyDelete
    Replies
    1. Oh, the XAML has been "consumed". Here it is without the less than & great than symbols:

      Grid Style="{StaticResource LayoutRootStyle}"
      pvl:PanView x:Name="PanningControl"
      Image x:Name="pannableImage" Stretch="UniformToFill" Source="{Binding FullSizedImage}" AutomationProperties.Name="{Binding Title}"
      /pvl:PanView
      /Grid

      Delete
    2. Philip, I was wondering if you might try placing the Image in a Canvas.
      PanView, Canvas, Image

      Delete
    3. Hi John

      That seems to have fixed the pinch/zoom and the image cropping problems.

      With regard to the initial starting zoom factor, am I correct that I need to calculate this myself? My concern here is getting the calculations right based on whether the app is currently snapped or not.

      Similarly, like others above, I want to constrain the panning so that the user cannot pan the image beyond the image's edges. I think I'm misunderstanding how you intend the Min & Max Translate properties to be used as I'm experimenting with setting the Min values to be 0 and all that means is that I cannot pan off to the left or the top, which isn't right if I've got bits of the image off to the right or bottom that I want to be able to see.

      Thanks.

      Philip

      Delete
  7. Hi John,
    Thanks for sharing this control its been really useful in a project i'm working on at the moment. I have this one issue maybe you can help me on.

    This is my XAML




    In C# I add a bunch of images to myCanvas which can be manipulated via gestureRecognizer to Scale, Rotate, and Translate.

    All of this is working fine. Now the issue is that i have a set of functions that arranges my images in various formations on the screen prior to using your PanView control I was arranging these images on a point that the user sets.

    However now that the parent is Transformable the point of reference at which these arrangements should form gets lost.

    Here is a simple bit of code that shows how i form a stack arrangement of images

    scale = 0.3 * System.Math.Min(
    (this.parent.ActualHeight / element.ActualHeight),
    (this.parent.ActualWidth / element.ActualWidth));
    element.RenderTransform = new CompositeTransform
    {
    TranslateX = ((2 * this._targetPt.X) - element.ActualWidth) / 2,
    TranslateY = ((2* this._targetPt.Y) - element.ActualHeight) / 2,

    CenterX = element.ActualWidth / 2,
    CenterY = element.ActualHeight / 2,

    ScaleX = scale,
    ScaleY = scale,

    Rotation = 0
    };


    this._targetPt is calculated on the Grid event handler and looks something like this

    Windows.UI.Input.PointerPoint ptrPt = e.GetCurrentPoint(null);
    this._targetPt = ptrPt.RawPosition;

    Now as you can see i get the RawPosition of the point of interaction and use it to be the anchor for which the center of my images translate to. How now what happens is that the panview transforms the scale which results in the image arrangement to be formed at the wrong location.

    Is there any way to do retrieve the scale factor and rotation from PanView to fix this issue. Or is there a better way to do this.

    Thanks
    Taha

    ReplyDelete
  8. Looks like the XAML didnt come through
    pvl:PanView x:Name="myPanView" ManipulationMode="All"
    Canvas x:Name="myCanvas"
    pvl:PanView

    ReplyDelete
  9. Question, when I zoom in on a textblock, the textblock gets a bit shaky(i.e. the textblock bounces). Is there any way I can solve this?

    ReplyDelete
  10. Greetings. I would like to place use this control to create a drawing app where 1 finger draws while 2 fingers pans, zooms, and rotates the canvas. Is it possible to do this if I place the canvas inside this control?

    ReplyDelete
  11. Hi John
    very nice work on the PanView. Is there any way to prevent the content from scaling when I zoom? I want to achieve a behaviour similar to dropped pins on google maps which spread further apart when zooming in but at constant size. I know Bing Maps API has functionality like this but I don't really need all the map imagery plus there are license issues.
    Thanks for your help! Regards, Blomquist

    ReplyDelete

Note: Only a member of this blog may post a comment.