views:

527

answers:

2

Im writing a WPF application that has a zoom and pan ability, but what I want to also implement is the ability to zoom and pan "automatically" (via a button click).

I have the methods all defined to zoom and pan, but Im having trouble telling the app the desired X/Y coordinates for the panning.

Basically, I know that I want the control to be centered at a desired zoom level (say zoomed in 6X times), but the panning destination point is NOT the center point of the control because after the zooming, its been scaled.

Does anyone know a way of calculating the desired X/Y position to pan to, taking into account the zooming as well? Do I just scale the desired destination Point? It doesnt seem to work for me...

Thanks a lot

EDIT -- COMPLETED --

Here is now what I have which is working just fine :)

<Canvas x:Name="LayoutRoot" Background="{DynamicResource WindowBackground}" Width="1024" Height="768">
    <Canvas x:Name="ProductCanvas" Width="1024" Height="768">
        <Canvas.RenderTransform>
            <TransformGroup>
                <ScaleTransform/>
                <SkewTransform/>
                <RotateTransform/>
                <TranslateTransform />
            </TransformGroup>
        </Canvas.RenderTransform>
        <Rectangle x:Name="r1" Fill="White" Stroke="Black" Width="180" Height="103.5" Canvas.Left="131.5" Canvas.Top="121.5" MouseDown="r1_MouseDown"/>
        <Rectangle x:Name="r2" Fill="#FF942222" Stroke="Black" Width="180" Height="103.5" Canvas.Left="617.5" Canvas.Top="121.5" MouseDown="r2_MouseDown"/>
        <Rectangle x:Name="r3" Fill="#FF2B1E9F" Stroke="Black" Width="180" Height="103.5" Canvas.Left="131.5" Canvas.Top="408" MouseDown="r3_MouseDown"/>
        <Rectangle x:Name="r4" Fill="#FF1F6E1D" Stroke="Black" Width="180" Height="103.5" Canvas.Left="617.5" Canvas.Top="408" MouseDown="r4_MouseDown"/>
    </Canvas>
  </Canvas>

----C#----

    private void r1_MouseDown(object sender, MouseButtonEventArgs e1)
    {
        Rect bounds = r1.TransformToAncestor(ProductCanvas).TransformBounds(new Rect(0, 0, r1.ActualWidth, r1.ActualHeight));
        ZoomInAndPan(5, new Point(bounds.TopLeft.X + (bounds.Width / 2), bounds.TopLeft.Y + (bounds.Height / 2)));
    }

    private void r2_MouseDown(object sender, MouseButtonEventArgs e1)
    {
        Rect bounds = r2.TransformToAncestor(ProductCanvas).TransformBounds(new Rect(0, 0, r2.ActualWidth, r2.ActualHeight));
        ZoomInAndPan(5, new Point(bounds.TopLeft.X + (bounds.Width / 2), bounds.TopLeft.Y + (bounds.Height / 2)));
    }

    private void r3_MouseDown(object sender, MouseButtonEventArgs e1)
    {
        Rect bounds = r3.TransformToAncestor(ProductCanvas).TransformBounds(new Rect(0, 0, r3.ActualWidth, r3.ActualHeight));
        ZoomInAndPan(5, new Point(bounds.TopLeft.X + (bounds.Width / 2), bounds.TopLeft.Y + (bounds.Height / 2)));
    }

    private void r4_MouseDown(object sender, MouseButtonEventArgs e1)
    {
        Rect bounds = r4.TransformToAncestor(ProductCanvas).TransformBounds(new Rect(0, 0, r4.ActualWidth, r4.ActualHeight));
        ZoomInAndPan(5, new Point(bounds.TopLeft.X + (bounds.Width/2), bounds.TopLeft.Y + (bounds.Height/2)));
    }

    public void ZoomInAndPan(double zoomTo, Point translateTarget)
    {
        var group = (ProductCanvas.RenderTransform as TransformGroup);

        var zoomTransform = group.Children[0] as ScaleTransform;
        var translateTransform = group.Children[3] as TranslateTransform;

        Point center = new Point(512, 384);

        destinationPoint.X *= newScale;
        destinationPoint.Y *= newScale;

        var deltaX = center.X - (translateTarget.X);
        var deltaY = center.Y - (translateTarget.Y);

        translateTransform.BeginAnimation(TranslateTransform.XProperty, CreateZoomAnimation(deltaX));
        translateTransform.BeginAnimation(TranslateTransform.YProperty, CreateZoomAnimation(deltaY));

        zoomTransform.BeginAnimation(ScaleTransform.ScaleXProperty, CreateZoomAnimation(zoomTo));
        zoomTransform.BeginAnimation(ScaleTransform.ScaleYProperty, CreateZoomAnimation(zoomTo));
    }

    private DoubleAnimation CreateZoomAnimation(double toValue)
    {
        var da = new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(700)))
        {
            AccelerationRatio = 0.1,
            DecelerationRatio = 0.9
        };

        return da;
    }
+1  A: 

Initially your viewport is at (0,0) and both the image and the viewport is of size X by Y. You want to scale the size by some magnification factor, m so that your images is of size m*X by m*Y, but your viewport (the part you're showing) is still a rectangle of size X by Y, positioned at (0,0) on the image. So you need to move the view port.

If your image is now m*X by m*Y, you can find the midpoints by dividing each by two. You can then subtract half of the viewport size to get the upper left corner. Something like (m*X/2 - X/2, m*Y/2 - Y/2).

jeffamaphone
I understand what you are saying, however, Im still confused as to how to calculate the desired translation in order to animate that translation so that the desired point is in the center of the screen.
Mark
+1  A: 

You're talking about a transformation - a pan and a scale.

You can do this a couple of different ways, but since you are using WPF, is there a reason you can't just use the RenderTransforms?

var pointClicked = (where user clicked)
var myWindow = (whatever your window is);

myWindow.RenderTransform = new TransformGroup();
var pan = new TranslateTransform(pointClicked.X, pointClicked.Y);
var scale = new ScaleTransform(6.0,6.0);
myWindow.RenderTransform.Children.Add(pan);
myWindow.RenderTransform.Children.Add(scale);

If you don't want to go that route, you need to do the 2D transformation "by hand": but do the pan first, then the scale. Transformations are not usually communitive; you'll get wrong results if you do them in a different order.

JerKimball
I have the transforms working fine, I'm issue is that I cannot calculate the desired X/Y translation in order to get a point to pan into the center of the screen.
Mark
Thats what I was referring to with the order of operations - have you tried the translation first (ie, new center = clickpoint.X, clickpoint.Y) and then performing the scale?
JerKimball
Put another way: If I click on point (50,50), I should first recenter my view on point (50,50), then perform the zoom scaling. If you do it the other way around, in order to translate to the right point, you'd have to transform the original translation by the scale...that sounds more confusing than it is. Basically, in order to figure out the deltaX/deltaY post-scaling, you need to take what the deltaX/deltaY *would have been* and then scale that vector by your zoom transform.
JerKimball
hmmm that sound like the right idea... Ill give that a shot :)
Mark
Can you please take a look at my edit on my post?
Mark
You're *almost* there - you only need to transform the translation vector: deltaX = center.X - translateTarget.X * zoomTo; deltaY = center.Y - translateTarget.Y * zoomTo; and remove the /= lines - that's the opposite of what you need.
JerKimball
done and works! thanks a lot for the help, this will be a big help to the project!
Mark