How to set panning limits on WPF Canvas based on zoom level?

时间秒杀一切 提交于 2021-02-10 20:48:14

问题


I'm trying to set zooming and panning limits on a control I found here: https://wpfextensions.codeplex.com

I managed to set zooming limits, but now I'm having trouble setting the panning limits so that you can't pan the object inside the canvas, outside the view.

I succeeded in setting the limits, but only when the zoom level is 1 (Zoom == 1, so no zoom), but the moment you increase the zoom (by rotating the mouse wheel) things start to go wrong: the limits are set, but they are not set correctly.

In order to set them correctly I have to take into consideration the deltaZoom (the amount zoom changed compared to the previous zoom value).

Small demo project

I have created a simple, standalone project where I can reproduce the issue: https://github.com/igorpopovio/CanvasZoomPan

The project shows a desktop window with the ZoomControl (canvas with ScaleTransform, TranslateTransform and a bunch of dependency properties to make it easier to work with the transforms). The ZoomControl contains a red square and the window contains the ZoomControl and a debug list of properties so I can see live how they change based on left click drag and mouse wheel zoom.

Expected vs actual behaviour

Expected behaviour: object/red square edge cannot get out of the current view.


Actual behaviour: object/red square edge gets out of the current view (still has limits, but aren't correctly set).

Code explanations

All the action happens in this file and the important bits are:

  • the panning limits: MinTranslateX, MaxTranslateX; MinTranslateY, MaxTranslateY
  • the current panning: TranslateX, TranslateY
  • the current zoom: Zoom
  • the amount zoom changed: deltaZoom (local variable)
  • the Zoom_PropertyChanged method
  • the LimitZoomingAndPanning method

What I tried

In the LimitZoomingAndPanning method I set the translation/panning limits which are working for Zoom == 1 (deltaZoom == 1), but are giving incorrect limits for any other Zoom values:

MinTranslateX = box.BottomLeft.X * deltaZoom;
MinTranslateY = box.BottomLeft.Y * deltaZoom;

MaxTranslateX = ActualWidth - box.Size.Width * deltaZoom;
MaxTranslateY = ActualHeight - box.Size.Height * deltaZoom;

The box variable is actually the bounding box of the object inside the canvas. ActualWidth and ActualHeight are the size of the canvas on which the object is rendered.

Logically, all the translation/panning limits should depend on deltaZoom.

Maybe I'm missing something?


回答1:


I originally tried doing the same thing with the ZoomAndPanControl but wasn't able to achieve what I wanted so I ended up writing my own control to provide a constrained zoom and pan control.

I have packaged this control up on nuget but it can be found on my gist and on my github with a demo project loading an image into the viewport control.

PM > Install-Package Han.Wpf.ViewportControl

Usage:

<controls:Viewport MinZoom="1" MaxZoom="50" ZoomSpeed="1.1">

    <Grid width="1200" height="1200">

        <Button />

    </Grid>

</controls:Viewport

and add the theme to the app.xaml:

<Application.Resources>

    <ResourceDictionary Source="pack://application:,,,/Han.Wpf.ViewportControl;component/Themes/Generic.xaml" />

</Application.Resources>

I know your not using the MatrixTransform but to constrain the control to the bounds of the parent is calculated like below:

    private void OnMouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (IsEnabled)
        {
            var scale = e.Delta > 0 ? ZoomSpeed : 1 / ZoomSpeed;
            var position = e.GetPosition(_content);

            var x = Constrain(scale, MinZoom / _matrix.M11, MaxZoom / _matrix.M11);
            var y = Constrain(scale, MinZoom / _matrix.M22, MaxZoom / _matrix.M22);

            _matrix.ScaleAtPrepend(x, y, position.X, position.Y);

            ZoomX = _matrix.M11;
            ZoomY = _matrix.M22;

            Invalidate();
        }
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (IsEnabled && _capture)
        {
            var position = e.GetPosition(this);

            var point = new Point
            {
                X = position.X - _origin.X,
                Y = position.Y - _origin.Y
            };

            var delta = point;
            _origin = position;

            _matrix.Translate(delta.X, delta.Y);

            Invalidate();
        }
    }

In the Invalidate call Constrain()

    private double Constrain(double value, double min, double max)
    {
        if (min > max)
        {
            min = max;
        }

        if (value <= min)
        {
            return min;
        }

        if (value >= max)
        {
            return max;
        }

        return value;
    }

    private void Constrain()
    {
        var x = Constrain(_matrix.OffsetX, _content.ActualWidth - _content.ActualWidth * _matrix.M11, 0);
        var y = Constrain(_matrix.OffsetY, _content.ActualHeight - _content.ActualHeight * _matrix.M22, 0);

        _matrix = new Matrix(_matrix.M11, 0d, 0d, _matrix.M22, x, y);
    }


来源:https://stackoverflow.com/questions/47771871/how-to-set-panning-limits-on-wpf-canvas-based-on-zoom-level

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!