0

I'm using this project as a reference: https://www.codeproject.com/Articles/168176/Zooming-and-panning-in-WPF-with-fixed-focus , but I want to limit the image from being panned off screen. I'd like panning to stop in an axis when the edge of the image is some fixed distance from the edge of the border. For example, the right edge of the image should always be at least +70 from the left edge of the border.

I've found some answered and unanswered questions, but this one seems to get me closer:
wpf-image-panning-constraints

I've modified the mouse move code as follows:

    private void image_MouseMove(object sender, MouseEventArgs e)
    {
        if (!image.IsMouseCaptured) return;
        Point p = e.MouseDevice.GetPosition(border);

        Point relativePoint = image.TransformToAncestor(border)
                      .Transform(new Point(0, 0));

        

        Matrix m = image.RenderTransform.Value;
        var xDelta = p.X - start.X;
        var yDelta = p.Y - start.Y;
        m.OffsetX = origin.X + (xDelta);
        m.OffsetY = origin.Y + (yDelta);

        var currentImageBounds = ImageBounds();
        var relativePosition  = image.TransformToAncestor(border).Transform(new Point(0, 0));
        if (currentImageBounds.Right <= 70 && xDelta<0)
        {
            //I'm messing with this code to modify/fix the x offset  
            m.OffsetX = origin.X;//CurrentImageBounds.Left + 70 ; //+ CurrentImageBounds.Width+70;
        }
        image.RenderTransform = new MatrixTransform(m);
    }


    private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (image.IsMouseCaptured) return;
        image.CaptureMouse();

        start = e.GetPosition(border);
        origin.X = image.RenderTransform.Value.OffsetX;
        origin.Y = image.RenderTransform.Value.OffsetY;
    }

    private Rect ImageBounds()
    {
        var rect = new Rect(image.RenderSize);
        var bounds = image.TransformToAncestor(border).TransformBounds(rect);
        //CurrentImageBounds = bounds;
        Debug.WriteLine($"ImageRight: {bounds.Right}");
        return bounds;
    }

When I get to the 70px threshold, the image just bounces around on the edge. I'm missing something here and stuck. Panning Is Jumping

How do I accomplish this?

GisMofx
  • 982
  • 9
  • 27

1 Answers1

1

Here is a short example of zooming and panning an Image element in a Canvas. It handles the mouse events on the Canvas, not on the Image.

Take a look at the code block if (translateConstraint > 0) { ... } in the MouseMove event handler, where the possible amount of translation in each direction is limited. The bounds of the transformed Image element are determined by using the Matrix property of the MatrixTransform in the Image#s RenderTransform.

Something similar would have to be added to the MouseWheel event handler.

It is also worth to note that the code does not reassign the Image's RenderTransform on each update, but just reassigns the Matrix property of the MatrixTransform that is assigned in XAML.

XAML:

<Canvas Background="Transparent"
        MouseLeftButtonDown="CanvasMouseLeftButtonDown"
        MouseLeftButtonUp="CanvasMouseLeftButtonUp"
        MouseMove="CanvasMouseMove"
        MouseWheel="CanvasMouseWheel">
    <Image x:Name="image" Stretch="None">
        <Image.RenderTransform>
            <MatrixTransform/>
        </Image.RenderTransform>
    </Image>
</Canvas>

Code behind:

private Point? mousePosition;
private int translateConstraint = 70;

private void CanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var canvas = (Canvas)sender;
    if (canvas.CaptureMouse())
    {
        mousePosition = e.GetPosition(canvas);
    }
}

private void CanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    var canvas = (Canvas)sender;
    canvas.ReleaseMouseCapture();
    mousePosition = null;
}

private void CanvasMouseMove(object sender, MouseEventArgs e)
{
    if (mousePosition.HasValue)
    {
        var canvas = (Canvas)sender;
        var p = e.GetPosition(canvas);
        var matrix = ((MatrixTransform)image.RenderTransform).Matrix;
        var dx = p.X - mousePosition.Value.X;
        var dy = p.Y - mousePosition.Value.Y;
        mousePosition = p;

        if (translateConstraint > 0)
        {
            var imageSize = new Point(image.ActualWidth, image.ActualHeight);
            var imageBounds = new Rect(
                matrix.Transform(new Point()),
                matrix.Transform(imageSize));

            var dxMin = translateConstraint - imageBounds.Right;
            var dxMax = canvas.ActualWidth - translateConstraint - imageBounds.Left;
            dx = Math.Min(Math.Max(dx, dxMin), dxMax);

            var dyMin = translateConstraint - imageBounds.Bottom;
            var dyMax = canvas.ActualHeight - translateConstraint - imageBounds.Top;
            dy = Math.Min(Math.Max(dy, dyMin), dyMax);
        }

        matrix.Translate(dx, dy);
        ((MatrixTransform)image.RenderTransform).Matrix = matrix;
    }
}

private void CanvasMouseWheel(object sender, MouseWheelEventArgs e)
{
    var canvas = (Canvas)sender;
    var p = e.GetPosition(canvas);
    var matrix = ((MatrixTransform)image.RenderTransform).Matrix;
    var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); // choose appropriate scaling factor

    matrix.ScaleAt(scale, scale, p.X, p.Y);
    ((MatrixTransform)image.RenderTransform).Matrix = matrix;
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • I'm going to try this. Are the tags inside the `` tag in xaml necessary? – GisMofx Aug 17 '23 at 11:14
  • You mean the assignment of the RenderTransform? You can also do that in code behind. The code in the answer requires that a MatrixTransform has been assigned to the RenderTransform in advance. – Clemens Aug 17 '23 at 11:19
  • Yes. What does the code behind equivalent look like? – GisMofx Aug 17 '23 at 21:39
  • The `translateConstraint` code makes sense. Does this require the image to be put into a canvas control. I'm trying the code with my image inside the border control and it's still not functioning correctly. Image is bouncing around the screen when panning. – GisMofx Aug 18 '23 at 01:42
  • A Canvas is the typical Panel element used for absolute positioning. When trying this with a Border, zoom and pan also works, but the initial size of the image is clipped to the size of the Border. – Clemens Aug 18 '23 at 05:05
  • So where I'm confused is when we calculate the `imageBounds` it we get the position and size of the image, but then the image it jumping all over the place. It's almost like the image is permitted to go below the threshold and then gets translated back to it. This behavior yield that bouncing around of the image. You said a key phrase: Absolute positioning; is a border different? I'm always computing position of the image relative to the border. Is there something different about the canvas? – GisMofx Aug 18 '23 at 10:12
  • When you copy the code exactly from this answer, there is nothing "*jumping all over the place*". It is unclear which "*position computing*" you are doing. With `e.GetPosition(canvas)` you do get coordinates relative to the Canvas. – Clemens Aug 18 '23 at 10:15
  • I copied and pasted. It works as expected. Smooth! I had to change one line: `((MatrixTransform)image.RenderTransform).Matrix = matrix;` quietly crashed the app. I used this: `image.RenderTransform = new MatrixTransform(matrix);` I enabled "ClipToBounds" but the image diappeared. Found this answer to resolve it: https://stackoverflow.com/a/6687047/997088 I'm still curious, why does my example not work? – GisMofx Aug 19 '23 at 12:43
  • Because you did not copy the XAML. That's what I was saying before: a MatrixTransform has to be assigned initially. Changing its Matrix property instead of creating a new MatrixTransform each time is more efficient. – Clemens Aug 19 '23 at 12:58
  • I copied the xaml verbatim. How could the transform be defined in code-behind? – GisMofx Aug 19 '23 at 16:51
  • 1
    "*How could the transform be defined in code-behind?*" Like you already do, but somewhere in the constructor after InitializeComponent: `image.RenderTransform = new MatrixTransform();` – Clemens Aug 19 '23 at 17:06
  • That worked! Thanks. – GisMofx Aug 19 '23 at 17:20
  • It is identical to what was declared in XAML, so that would have also worked. – Clemens Aug 19 '23 at 19:37