1

I know there are several posts on stack and others websites, but it seems I still make something wrong. When I zoom with MouseWheel event, the zoom is always not centered, but the left side of my canvas always stays on the left on my ViewBox, so when I zoom in, I only can see the left of my canvas.

XAML code :

<Grid x:Name="MainGrid">
        <Viewbox x:Name="ViewBoxDessin" Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Canvas  x:Name="monDessin" Background="WhiteSmoke" MouseWheel="monDessin_MouseWheel">
                <Canvas.LayoutTransform>
                    <ScaleTransform x:Name="st" ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5" />
                </Canvas.LayoutTransform>
            </Canvas>
        </Viewbox>
        <Viewbox x:Name="ViewBoxDessin2" Stretch="None">
            <Canvas x:Name="monDessin2">
                <Canvas.LayoutTransform>
                    <ScaleTransform x:Name="st2" ScaleX="1" ScaleY="1" CenterX=".5" CenterY=".5" />
                </Canvas.LayoutTransform>
            </Canvas>
        </Viewbox>
    </Grid>

Code behind

public AfficheGraphiquePiece()
        {
            InitializeComponent();
            MakeMyDrawing();
            ViewBoxDessin.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
            ViewBoxDessin.Height = System.Windows.SystemParameters.PrimaryScreenHeight;

            double ech_x = monDessin.Width / System.Windows.SystemParameters.PrimaryScreenWidth;
            double ech_y = monDessin.Height / System.Windows.SystemParameters.PrimaryScreenHeight;
            double ech = Math.Min(ech_x, ech_y);
            this.ech_full = ech;
            st.ScaleX = ech;
            st.ScaleY = -ech;
            st2.ScaleX = ech;
            st2.ScaleY = ech;
        }
        private void monDessin_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            double zoom = e.Delta > 0 ? 1.1 : 0.9;
            if(st.ScaleX<this.ech_full*1.1 && zoom<1)
            {
                st.ScaleX = this.ech_full;
                st.ScaleY = -this.ech_full;
            }
            else
            {
                st.ScaleX *= zoom;
                st.ScaleY *= zoom;
                double coor_x = Mouse.GetPosition(monDessin).X;
                double coor_y = Mouse.GetPosition(monDessin).Y;
                st.CenterX = coor_x;
                st.CenterY = coor_y;

            }

        }

Excuse me, didn't remove some code, and it could make confusion, just replaced it by a function MakeMyDrawing()

Well, after Clemens advise, and help of that link for matrix use, I could do the following :

XAML :

<Grid x:Name="MainGrid">
        <Canvas  x:Name="monDessin" Background="WhiteSmoke" MouseWheel="monDessin_MouseWheel" MouseLeftButtonDown="image_MouseLeftButtonDown" MouseMove="image_MouseMove" MouseLeftButtonUp="image_MouseLeftButtonUp" MouseLeave="image_MouseLeave" >
            <Canvas.RenderTransform >
                <MatrixTransform/>
            </Canvas.RenderTransform>
        </Canvas>
        <Canvas x:Name="monDessin2">
            <Canvas.RenderTransform >
                <MatrixTransform/>
            </Canvas.RenderTransform>
        </Canvas>
    </Grid>

Code behind

public AfficheGraphiquePiece(Repere rep)
        {
            InitializeComponent();
            ClassGraphique monGraphe = new ClassGraphique(monDessin);
            ClassGraphique monGraphe2 = new ClassGraphique(monDessin2);
            MakeMyDrawing();
            double screenWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
            double screenHeight = System.Windows.SystemParameters.PrimaryScreenHeight;

            double ech_x = screenWidth/ monDessin.Width ;
            double ech_y = screenHeight/ monDessin.Height;
            double ech = Math.Min(ech_x, ech_y)*0.9;
            this.ech_full = ech;
            this.echelleNow = this.ech_full;
            MatrixTransform maTrans =(MatrixTransform)monDessin.RenderTransform;
            var mat = maTrans.Matrix;
            mat.ScaleAt(ech, -ech, 0.1* screenWidth, (screenHeight-monDessin.Height*ech)/2-0.1*screenHeight);
            MatrixTransform maTrans2 = (MatrixTransform)monDessin2.RenderTransform;
            var mat2 = maTrans2.Matrix;
            mat2.ScaleAt(ech, ech, 0.1 * screenWidth, screenHeight*ech-((screenHeight - monDessin.Height * ech) / 2 - 0.1 * screenHeight));
            maTrans.Matrix = mat;
            maTrans2.Matrix = mat2;
        }
        private void monDessin_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            try
            {
                var position = e.GetPosition(monDessin);
                MatrixTransform transform = (MatrixTransform)monDessin.RenderTransform;
                MatrixTransform transform2 = (MatrixTransform)monDessin2.RenderTransform;
                var matrix = transform.Matrix;
                var matrix2 = transform2.Matrix;
                var scale = e.Delta >= 0 ? 1.1 : (1.0 / 1.1); 
                this.echelleNow *= scale;
                matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
                matrix2.ScaleAtPrepend(scale, scale, position.X,monDessin.Height-position.Y);
                monDessin.RenderTransform = new MatrixTransform(matrix);
                monDessin2.RenderTransform = new MatrixTransform(matrix2);
            }
            catch { }
        }
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34
  • 1
    Remove the Viewbox and set RenderTransform instead of LayoutTransform. Use a MatrixTransform where you can control zoom and pan (i.e. scaling and translation). – Clemens Nov 03 '18 at 14:58
  • thanks, in fact I saw on stack a n answer advising to use matrix, but it was not working. After removing viewbox and LayoutTransform all seems to work correctly, thanks – Siegfried.V Nov 03 '18 at 18:06
  • @Clemens excuse me, but I see I meet an error on mat.ScaleAt() at the beginning, and I cannot see how this is working. I have canvas with width going from 500 to 12000, and want to make it appear on full screen(90% to keep a margin), but something going wrong – Siegfried.V Nov 04 '18 at 11:22
  • Not sure what "*something going wrong*" is supposed to mean exactly. As a general note, you don't need to apply a new MatrixTransform each time. Just assign a new Matrix value like `((MatrixTransform)monDessin.RenderTransform).Matrix = mat;`. Also, using `as` instead of an explicit cast is wrong. If there would be no MatrixTransform in the RenderTransform property your current code would throw a NullReferenceException instead of the correct InvalidCastException. – Clemens Nov 04 '18 at 11:38
  • well excuse me, "something going wrong" sure doesn't help, what I meant is my mat.ScaleAt() at the beginning is wrong, 1st and 2nd parameters may be the scale, and what are supposed to be 3rd and 4th? on doc.microsoft it is written CenterX and CenterY, but I don't manage to set them correctly, I tried many things, as just 0.5, screen Height/2, canvas height/2... but for each drawing the result is different, depending on canvas size – Siegfried.V Nov 04 '18 at 12:44
  • regarding your last comment, honestly I don't manage good yet the matrix thing, it is totally new to me, so I am trying to do it reading on several posts/sites... but nothing easy – Siegfried.V Nov 04 '18 at 12:47
  • Just a guess, but center should be (0,0) at the beginning. – Clemens Nov 04 '18 at 12:48
  • if I put center to (0,0), then the drawing appears in the top left corner of the window. To make it easier for me(for now) I create one window in full screen, but my Canvas can be any Length/width, that's why I calculate the two scales(vertical and horizontal) then keep the smaller, cause I want keep proportions, then I want to center it on the screen, if I understood, "Center" is not center, but origin of canvas(0,0) means in the top left corner, as I have a ScaleY=-1. What I want is to get it centered in my window, that's why I tried values as screenWidth/2, canvas.Width/2... – Siegfried.V Nov 04 '18 at 12:57
  • Just noticed, even if I set center to (0,0), it doesn't always work, in one case the canvas will be in top left corner as said before, but sometimes canvas is out of the window, all depends on my canvas dimensions – Siegfried.V Nov 04 '18 at 13:17

1 Answers1

3

Here is a very basic example for zooming and panning a Canvas with fixed initial size. The MatrixTransform in the RenderTransform of the inner Canvas provides the necessary transformations, while the outer Canvas handles mouse input and sets an initial scaling.

<Canvas Background="Transparent"
        SizeChanged="ViewportSizeChanged"
        MouseLeftButtonDown="ViewportMouseLeftButtonDown"
        MouseLeftButtonUp="ViewportMouseLeftButtonUp"
        MouseMove="ViewportMouseMove"
        MouseWheel="ViewportMouseWheel">

    <Canvas x:Name="canvas" Width="1000" Height="600">
        <Canvas.RenderTransform>
            <MatrixTransform x:Name="transform"/>
        </Canvas.RenderTransform>

        <Ellipse Fill="Red" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100"/>
        <Ellipse Fill="Green" Width="100" Height="100" Canvas.Right="100" Canvas.Bottom="100"/>
    </Canvas>
</Canvas>

Code behind:

private Point? mousePos;

private void ViewportSizeChanged(object sender, SizeChangedEventArgs e)
{
    ((MatrixTransform)canvas.RenderTransform).Matrix = new Matrix(
        e.NewSize.Width / canvas.ActualWidth,
        0, 0,
        e.NewSize.Height / canvas.ActualHeight,
        0, 0);
}

private void ViewportMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var viewport = (UIElement)sender;
    viewport.CaptureMouse();
    mousePos = e.GetPosition(viewport);
}

private void ViewportMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    ((UIElement)sender).ReleaseMouseCapture();
    mousePos = null;
}

private void ViewportMouseMove(object sender, MouseEventArgs e)
{
    if (mousePos.HasValue)
    {
        var pos = e.GetPosition((UIElement)sender);
        var matrix = transform.Matrix;
        matrix.Translate(pos.X - mousePos.Value.X, pos.Y - mousePos.Value.Y);
        transform.Matrix = matrix;
        mousePos = pos;
    }
}

private void ViewportMouseWheel(object sender, MouseWheelEventArgs e)
{
    var pos = e.GetPosition((UIElement)sender);
    var matrix = transform.Matrix;
    var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;
    matrix.ScaleAt(scale, scale, pos.X, pos.Y);
    transform.Matrix = matrix;
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • well in fact, your example works all without problem... I also noticed when you create a matrix you put not 4, but 6 arguments, can you please explain what they are? – Siegfried.V Nov 04 '18 at 17:34
  • ok will look further, anyway now it's working, seems I tried to make something difficult for the same result... thanks for your time – Siegfried.V Nov 04 '18 at 18:00
  • You're welcome. I think the important point is that you handle mouse events in an outer element that is not itself transformed. – Clemens Nov 04 '18 at 18:01