Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scale canvas to mouse position

Tags:

c#

wpf

I am trying to implement a zoom-functionality for a canvas using the mouse wheel. Currently I am just Zooming to the center position of the canvas using CenterX="0.5" and CenterY="0.5". I would like to change the behavior so that the zooming happens at the mouse position and I would like to know if this is possible with a ScaleTransform.

Currently I use the following code:

<Canvas Width="500" Height="500">
    <Canvas.LayoutTransform>
        <ScaleTransform CenterX="0.5" CenterY="0.5"
                                ScaleX="{Binding Zoom}"
                                ScaleY="{Binding Zoom}" />
    </Canvas.LayoutTransform>
</Canvas>
like image 465
Eggi Avatar asked Mar 12 '14 11:03

Eggi


2 Answers

A very basic approach to zoom a Canvas (or any other UIElement) at a specific position would be to use a MatrixTransform for the RenderTransform property

<Canvas Width="500" Height="500" MouseWheel="Canvas_MouseWheel">
    <Canvas.RenderTransform>
        <MatrixTransform/>
    </Canvas.RenderTransform>
</Canvas>

and update the Matrix property of the transform like in this MouseWheel handler:

private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
    var element = (UIElement)sender;
    var position = e.GetPosition(element);
    var transform = (MatrixTransform)element.RenderTransform;
    var matrix = transform.Matrix;
    var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); // choose appropriate scaling factor

    matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
    transform.Matrix = matrix;
}
like image 101
Clemens Avatar answered Oct 31 '22 15:10

Clemens


I spent the past two days agonizing over this issue and I figured it out. This will get you smooth zooming in toward the mouse and smooth zooming out. I'm posting my solution here for anyone who might search and stumble back here.

// Class constructor
public YourClass(Canvas theCanvas) //You may not need the Canvas as an argument depending on your scope
    {
        panTransform = new TranslateTransform();
        zoomTransform = new ScaleTransform();
        bothTransforms = new TransformGroup();

        bothTransforms.Children.Add(panTransform);
        bothTransforms.Children.Add(zoomTransform);

        theCanvas.RenderTransform = bothTransforms;

        //Handler
        theCanvas.MouseWheel += wheelEvent;
        //You also need your own handlers for panning, which I'm not showing here.
    }

private void returnCalculatedScale()
    {
        double d;
        //Do some math to get a new scale. I keep track of an integer, and run it through the formula y^(x/3) where X is the integer.

        return d;
    }


// Mouse wheel handler, where the magic happens
private void wheelEvent(object sender, MouseWheelEventArgs e)
    {
        Point position = e.GetPosition(mainCanvas);           

        zoomTransform.CenterX = position.X;
        zoomTransform.CenterY = position.Y;

        zoomTransform.ScaleX = returnCalculatedScale();
        zoomTransform.ScaleY = returnCalculatedScale();

        Point cursorpos = Mouse.GetPosition(mainCanvas); //This was the secret, as the mouse position gets out of whack when the transform occurs, but Mouse.GetPosition lets us get the point accurate to the transformed canvas.

        double discrepancyX = cursorpos.X - position.X;
        double discrepancyY = cursorpos.Y - position.Y;

        //If your canvas is already panned an arbitrary amount, this aggregates the discrepancy to the TranslateTransform.
        panTransform.X += discrepancyX;
        panTransform.Y += discrepancyY;
like image 41
NWoodsman Avatar answered Oct 31 '22 15:10

NWoodsman