3
<utils:ScrollViewer x:Name="ImageViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Grid.Row="2"                                                                   
                                       CurrentHorizontalOffset="{Binding ScrollHorizontalValue, Mode=TwoWay}"
                                       CurrentVerticalOffset="{Binding ScrollVerticalValue, Mode=TwoWay}"                                        
                                       >
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="PreviewMouseWheel">
                            <cmd:EventToCommand Command="{Binding MouseWheelZoomCommand}" PassEventArgsToCommand="True"/>
                        </i:EventTrigger>
                        <i:EventTrigger EventName="ScrollChanged">
                            <cmd:EventToCommand Command="{Binding ScrollChangedCommand}" PassEventArgsToCommand="True"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <Grid Background="{StaticResource ThatchBackground}" RenderTransformOrigin="0.5,0.5">
                        <ItemsControl ItemsSource="{Binding CanvasItems}" ItemTemplate="{StaticResource templateOfROI}">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <Canvas x:Name="BackPanel"
                                        Width="{Binding DataContext.ImageWidth, ElementName=MainGrid}" 
                                        Height="{Binding DataContext.ImageHeight, ElementName=MainGrid}"
                                        ClipToBounds="True">
                                        <Canvas.Background>
                                            <ImageBrush x:Name="BackImage"
                                                        ImageSource="{Binding DataContext.SelectedImage.Path, ElementName=MainGrid}"/>
                                        </Canvas.Background>

                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="MouseRightButtonDown">
                                                <cmd:EventToCommand Command="{Binding DataContext.MouseRightCommand, ElementName=MainGrid}"/>
                                            </i:EventTrigger>
                                            <i:EventTrigger EventName="MouseLeftButtonDown">
                                                <cmd:EventToCommand Command="{Binding DataContext.MouseMoveStartCommand, ElementName=MainGrid}" PassEventArgsToCommand="True"/>
                                            </i:EventTrigger>
                                            <i:EventTrigger EventName="MouseMove">
                                                <cmd:EventToCommand Command="{Binding DataContext.MouseMovingCommand, ElementName=MainGrid}" PassEventArgsToCommand="True"/>
                                            </i:EventTrigger>
                                            <i:EventTrigger EventName="MouseRightButtonUp">
                                                <cmd:EventToCommand Command="{Binding DataContext.MouseMoveEndCommand, ElementName=MainGrid}"/>
                                            </i:EventTrigger>
                                            <i:EventTrigger EventName="MouseLeave">
                                                <cmd:EventToCommand Command="{Binding DataContext.MouseLeaveCommand, ElementName=MainGrid}"/>
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>

                                        <Canvas.LayoutTransform>
                                            <TransformGroup>
                                                <ScaleTransform ScaleX="{Binding ScaleX}"
                                                                ScaleY="{Binding ScaleY}">
                                                </ScaleTransform>
                                        </TransformGroup>
                                    </Canvas.LayoutTransform>
                                </Canvas>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>
                </Grid>
            </utils:ScrollViewer>

The reference point is fixed with Left and Top points to zoom the Canvas. I'd like to zoom in and zoom out on the mouse pointer. How do I make it into the MVVM Pattern? (Not Behind code). Using mouse wheel, I can zoom in canvas. I already use RenderTransformOrigin, CenterX, CenterY but it is not worked. I think I made a wrong approach. Please help me..

user70976
  • 41
  • 1
  • 4
  • 1
    Where is your code that handles the zooming? Even when it's not working as expected, you should still include it as your current approach. – grek40 Sep 26 '17 at 11:17

2 Answers2

16

Since you didn't provide your current zooming code, here is a generic example of zooming on the mouse position:

<Grid x:Name="grid1" Background="White" MouseWheel="Grid_MouseWheel">
    <Grid x:Name="grid2">
        <Grid.RenderTransform>
            <MatrixTransform/>
        </Grid.RenderTransform>
        <Rectangle Width="20" Height="20" Margin="20" VerticalAlignment="Top" HorizontalAlignment="Left" Fill="Green"/>
    </Grid>
</Grid>

With code that updates the transformation matrix:

private void Grid_MouseWheel(object sender, MouseWheelEventArgs e)
{
    var matTrans = grid2.RenderTransform as MatrixTransform;
    var pos1 = e.GetPosition(grid1);

    var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;

    var mat = matTrans.Matrix;
    mat.ScaleAt(scale, scale, pos1.X, pos1.Y);
    matTrans.Matrix = mat;
    e.Handled = true;
}
grek40
  • 13,113
  • 1
  • 24
  • 50
  • 1
    I hope you don't mind, but I added an answer, based on your code, in the form of a `Behavior` for people trying to stick to the MVVM pattern. – Bradley Uffner Sep 26 '17 at 13:12
  • @BradleyUffner I don't mind at all. A variety of good solutions helps SO as a whole and I'm more than happy with the way you mentioned my contribution ;) – grek40 Sep 26 '17 at 13:37
  • This is a very nice simple solution. My question is, is there a way to change how the scaled contents are filtered (i.e using nearest neighbour)? I'm using this with a canvas which has a tiled image background, and despite trying `RenderOptions.BitmapScalingMode` on the image, it has no effect, presumably because the render transform is applied afterwards. – Logix Nov 02 '20 at 18:03
  • Hi @Logix good question, but I don't know the answer. Maybe check whether https://stackoverflow.com/a/2913679/5265292 or one of the other answers specifically related to BitmapScalingMode helps. Otherwise you should probably open a new question – grek40 Nov 02 '20 at 18:51
12

I took @Grek40's code and converted it in to a Behavior for anyone trying to stick with the MVVM pattern. If you up-vote this, please remember to also up-vote his answer too, as mine is based on his work. It requires the System.Windows.Interactivity.WPF nuget package (or some other framework) for the Behavior base-class. You can apply this to any UIElement. It will automatically add the MatrixTransform for you, so you don't have to do that in XAML (it will overwrite any existing transform).

public class ZoomOnMouseWheel : Behavior<FrameworkElement>
{
    public Key? ModifierKey { get; set; } = null;
    public TransformMode TranformMode { get; set; } = TransformMode.Render;

    private Transform _transform;

    protected override void OnAttached()
    {
        if (TranformMode == TransformMode.Render)
        {
            _transform = AssociatedObject.RenderTransform = new MatrixTransform();
        }
        else
        {
            _transform = AssociatedObject.LayoutTransform = new MatrixTransform();
        }

        AssociatedObject.MouseWheel += AssociatedObject_MouseWheel;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseWheel -= AssociatedObject_MouseWheel;
    }

    private void AssociatedObject_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        if ((!ModifierKey.HasValue || !Keyboard.IsKeyDown(ModifierKey.Value)) && ModifierKey.HasValue)
        {
            return;
        }

        if (!(_transform is MatrixTransform transform))
        {
            return;
        }

        var pos1 = e.GetPosition(AssociatedObject);
        var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;
        var mat = transform.Matrix;
        mat.ScaleAt(scale, scale, pos1.X, pos1.Y);
        transform.Matrix = mat;
        e.Handled = true;
    }
}

public enum TransformMode
{
    Layout,
    Render,
}

You can use it like this:

<Grid>
    <interactivity:Interaction.Behaviors>
        <behaviors:ZoomOnMouseWheel />
    </interactivity:Interaction.Behaviors>
    <!--Your grid content here-->
</Grid>

Don't forget the xmlns:

xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • I've added the ability to only scroll when a modifier key, such as `LeftCtrl` is held down through the `ModifierKey` property, and the ability to control if the transform is done as a `RenderTranform` or a `LayoutTransform` through the `TransformMode` property. It defaults to `RenderTransform`, but if you place the element with the `Behavior` inside a scrollviewer, and set it to `LayoutTransform`, you can get scrollbars when you zoom in. – Bradley Uffner Sep 26 '17 at 14:04