Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep area of canvas centered in a ScrollViewer when Zoomed in or out, and not everything can be displayed in the viewing window

Tags:

c#

wpf

zooming

xaml

Everyone,

I have a WPF app that has a canvas that I have wrapped in a scroll Viewer. I have a slider in the status bar that allows the user to zoom in and out (just like Win 7's mspaint).

Here is some of the XAML:

<ScrollViewer Name="Map"
              VerticalScrollBarVisibility="Auto"
              HorizontalScrollBarVisibility="Auto">
    <Canvas x:Name="WallsCanvas" Height="800" Width="1000" ClipToBounds="True">
        <Canvas.LayoutTransform>
            <ScaleTransform x:Name="WallsCanvasScale"
                            ScaleX="1" ScaleY="1" />
        </Canvas.LayoutTransform>
    </Canvas>
</ScrollViewer>

When I zoom in, and the scrollbars are visible, the scrollbars, no matter where they are set, jump to the middle.

It is exactly as if the value of the scrollbars stay the same but the max value increases.

What can I do to get them to ... say, if they were in the lower right corner, to stay in the lower right corner after a zoom in or out?

BTW, here is my Zoom in and out code:

private void SliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    var scales = new []{.125, .25, .5, 1, 2, 4, 8};
    var scale = scales[(int)((Slider) sender).Value];

    ScaleChanged(scale, WallsCanvasScale);
}

private static void ScaleChanged(double scale, ScaleTransform st)
{
    st.ScaleX = scale;
    st.ScaleY = scale;
}

So, no rocket science in my code but ...

Update idea: If I had access to the value and the max value of the scrollbars, could I get the percentage between the two and then after the zooming (scaling) I could re-apply the value of the scrollbar as a percentage of the max value????? But where is the value and the max value available?

Any help would be appreciated. I cannot think that I am the only one that has this problem since MSPaint (the Windows 7 version) works correctly and I assume it is a XAML app.

Here is a link (http://www.leesaunders.net/examples/zoomexample/zoomexample.zip) to a minimum working example project (VS 2010). When you run it, just move the scroll bars then zoom in a level, you will see the issue right away.

like image 740
saunderl Avatar asked Jun 22 '11 22:06

saunderl


1 Answers

You just need to offset the shift which results from the scale because it scales from (0,0). It's a bit complicated but here's a sketch of what the method in your sample could look like:

private void SliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    var slider = (Slider)sender;
    if (!slider.IsLoaded)
    {
        slider.Loaded += (s, le) => SliderValueChanged(sender, e);
        return;
    }

    var scales = new[] { .125, .25, .5, 1, 2, 4, 8 };
    var scale = scales[(int)((Slider)sender).Value];

    // The "+20" are there to account for the scrollbars... i think. Not perfectly accurate.
    var relativeMiddle = new Point((Map.ActualWidth + 20) / 2, (Map.ActualHeight + 20) / 2);
    var oldLocation = CanvasScale.Transform(TemplateCanvas.PointFromScreen(relativeMiddle));

    ScaleChanged(scale, CanvasScale);

    var newLocation = CanvasScale.Transform(TemplateCanvas.PointFromScreen(relativeMiddle));

    var shift = newLocation - oldLocation;

    Map.ScrollToVerticalOffset(Map.VerticalOffset + shift.Y);
    Map.ScrollToHorizontalOffset(Map.HorizontalOffset + shift.X);

    lblScale.Content = scale.ToString("P1").Replace(".0", string.Empty);
}

Should be quite self-explanatory; the location of the center is measured before and after the scaling to calculate the shift of that point, which then is added to the current scroll-position.

like image 125
H.B. Avatar answered Nov 14 '22 01:11

H.B.