Search code examples
wpfxamlcanvasmvvmzooming

Zoom function in canvas with MVVM pattern in WPF


I have been converting my app to MVVM pattern. I am trying to figure out zoom in function.

There is canvas panel and ellipse in my project.

The problem is zoomIn function is working but only when mouse pointer is on the shape.

I think the size of canvas panel I created in XAML same as with shape size. So I need to apply this zoom function to the grid control that parent of canvas.I couldn't achieve this.

These are the XAML codes.

<Window x:Class="CanvasSampleMvvm.View.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:CanvasSampleMvvm.View"
        xmlns:model="clr-namespace:CanvasSampleMvvm.Model"
        xmlns:vm="clr-namespace:CanvasSampleMvvm.ViewModel"
        mc:Ignorable="d"
        Title="MainView" Height="450" Width="800">

    <Window.Resources>
        <vm:MainViewVM x:Key="vm"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource vm}">

        <ItemsControl ItemsSource="{Binding Path=Shapes}">

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseWheel">
                    <i:InvokeCommandAction Command="{Binding ZoomInCommand}" PassEventArgsToCommand="True"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
            
            <ItemsControl.Resources>
                <DataTemplate DataType="{x:Type model:mShape}">
                    <Path Data="{Binding Geometry}" Stroke="{Binding Stroke}" Fill="{Binding Fill}" RenderTransform="{Binding Transform}" />
                </DataTemplate>
            </ItemsControl.Resources>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas DataContext="{StaticResource vm}">
                        <Canvas.RenderTransform>
                            <ScaleTransform
                                ScaleX="{Binding Zoomlevel,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" 
                                ScaleY="{Binding Zoomlevel,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" 
                                CenterX="{Binding ZoomCenterX, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" 
                                CenterY="{Binding ZoomCenterY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                        </Canvas.RenderTransform>
                    </Canvas>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left" Value="{Binding Path=XPos}" />
                    <Setter Property="Canvas.Top" Value="{Binding Path=YPos}" />
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>

    </Grid>
</Window>

These are ViewModel Codes:

public class MainViewVM : INotifyPropertyChanged
{
    public ObservableCollection<mShape> Shapes { get; } = new ObservableCollection<mShape>();
    private readonly MatrixTransform transform = new MatrixTransform();
    public ZoomInCommand ZoomInCommand { get; set; }
    public MainViewVM()
    {
        ZoomInCommand = new ZoomInCommand(this);

        Shapes.Add(new mShape
        {
            XPos = 100,
            YPos = 100,
            Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x66, 0x66, 0x66)),
            Transform = transform,
            Geometry = new EllipseGeometry { RadiusX = 50, RadiusY = 50 },
            Fill = (SolidColorBrush)new BrushConverter().ConvertFrom("#D3D3D3")

    });
    }
    public void ZoomIn()
    {
        double scaleFactor = zoomLevel;

        Matrix scaleMatrix = Shapes[0].Transform.Matrix;

        scaleMatrix.ScaleAt(scaleFactor, scaleFactor, 0, 0);
        for (int i = 0; i < Shapes.Count; i++)
        {
            double x = Shapes[i].XPos;
            double y = Shapes[i].YPos;
            double sx = x * zoomLevel;
            double sy = y * zoomLevel;
            Shapes[i].XPos = sx;
            Shapes[i].XPos = sy;
            Shapes[i].Transform.Matrix = scaleMatrix;

        }
    }

    private double zoomLevel = 1.1;
    public double ZoomLevel
    {
        get { return zoomLevel; }
        set
        {
            zoomLevel = value;
            OnPropertyChanged("ZoomLevel");
        }
    }

    private double zoomCenterX;
    public double ZoomCenterX
    {
        get { return zoomCenterX; }
        set
        {
            zoomCenterX = value;
            OnPropertyChanged("ZoomCenterX");
        }
    }

    private double zoomCenterY;
    public double ZoomCenterY
    {
        get { return zoomCenterY; }
        set
        {
            zoomCenterY = value;
            OnPropertyChanged("ZoomCenterY");
        }
    }
    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;

}

These are Model Class (mShape)

public class mShape
{
    public double XPos { get; set; }
    public double YPos { get; set; }
    public MatrixTransform Transform { get; set; }
    public Geometry Geometry { get; set; }
    public Brush Stroke { get; set; }
    public Brush Fill { get; set; }
}

These are ZoomInCommand codes:

public class ZoomInCommand : ICommand
{
    event EventHandler ICommand.CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
    public MainViewVM VM { get; set; }
    public ZoomInCommand(MainViewVM vm)
    {
        VM = vm;
    }
    bool ICommand.CanExecute(object parameter)
    {
        return true;
    }
    void ICommand.Execute(object parameter)
    {
        VM.ZoomIn();
    }
}

Solution

  • Paint the Canvas with a Brush by setting the Background property of it:

    <Canvas Background="Transparent" DataContext="{StaticResource vm}">
        <Canvas.RenderTransform>
            <ScaleTransform
                ScaleX="{Binding Zoomlevel,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" 
                ScaleY="{Binding Zoomlevel,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" 
                CenterX="{Binding ZoomCenterX, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" 
                CenterY="{Binding ZoomCenterY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </Canvas.RenderTransform>
    </Canvas>
    

    This is required for it to receive the mouse input events.