Search code examples
c#wpfxamlmvvm

RenderTransfor binding without violating MVVM | wpf


I have Window with Button and Image:

<Window x:Class="MRE_WPF.MainWindow"
        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:VM="clr-namespace:MRE_WPF.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <VM:MainWindowViewModel/>
    </Window.DataContext>
    <Canvas Height="480" Name="MainCanvas">
        <Button x:Name="TransformButton"
                Background="Red"
                Height="75"
                Width="150"
                FontSize="20"
                Click="TransformButton_Click">
            <StackPanel Orientation="Horizontal">
                <Label Content="Transform"/>
            </StackPanel>
        </Button>
        <Image x:Name="car"
               Height="284"
               Canvas.Left="208"
               Canvas.Top="73"
               Width="188"
               Source="/MRE_WPF;component/images/car.png"
               RenderTransformOrigin="0.5,0.5"
               RenderTransform="{Binding ImageViewModel.TransformGroup}">
        </Image>
    </Canvas>
</Window>

Main window view model:

class MainWindowViewModel {
    public readonly ImageViewModel ImageViewModel = new();
}

And image view model:

using System.Runtime.CompilerServices;
using System.Windows.Media;

namespace MRE_WPF.ViewModels {
    class ImageViewModel:INotifyPropertyChanged {
        public event PropertyChangedEventHandler? PropertyChanged;

        public ImageViewModel() {
            transformGroup = new TransformGroup();
            rotationAngle = 0;
            rotateTransform = new RotateTransform(rotationAngle);
            translateTransformX = 0;
            translateTransformY = 0;
            translateTransform = new TranslateTransform(translateTransformX, translateTransformY);
            transformGroup.Children.Add(rotateTransform);
            transformGroup.Children.Add(translateTransform);
        }

        double rotationAngle;
        public double RotationAngle {
            get => rotationAngle;
            set {
                if(rotationAngle != value) {
                    rotationAngle = value;
                    RotateTransform = new RotateTransform(rotationAngle);
                }
            }
        }

        RotateTransform rotateTransform;

        public RotateTransform RotateTransform {
            get => rotateTransform;
            set {
                rotateTransform = value;
                var temp = new TransformGroup();
                temp.Children.Add(RotateTransform);
                temp.Children.Add(TranslateTransform);
                TransformGroup = temp.Clone();

                OnPropertyChanged(nameof(RotationAngle));
            }
        }

        double translateTransformX;
        public double TranslateTransformX {
            get => translateTransformX;
            set {
                if(translateTransformX != value) {
                    translateTransformX = value;
                    TranslateTransform = new TranslateTransform(translateTransformX, translateTransformY);
                }
            }
        }

        double translateTransformY;
        public double TranslateTransformY {
            get => translateTransformY;
            set {
                if(translateTransformY != value) {
                    translateTransformY = value;
                    TranslateTransform = new TranslateTransform(translateTransformX, translateTransformY);
                }
            }
        }

        TranslateTransform translateTransform;
        public TranslateTransform TranslateTransform {
            get => translateTransform;
            set {
                translateTransform = value;
                var temp = new TransformGroup();
                temp.Children.Add(RotateTransform);
                temp.Children.Add(TranslateTransform);
                TransformGroup = temp.Clone();
                OnPropertyChanged(nameof(TranslateTransformX));
                OnPropertyChanged(nameof(TranslateTransformY));
            }
        }

        TransformGroup transformGroup;
        public TransformGroup TransformGroup {
            get => transformGroup;
            set {
                transformGroup = value;
                OnPropertyChanged(nameof(RotateTransform));
                OnPropertyChanged(nameof(TranslateTransform));
                OnPropertyChanged(nameof(TransformGroup));
            }
        }

        public void OnPropertyChanged([CallerMemberName] string name = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

And button click handler:

private void TransformButton_Click(object sender, RoutedEventArgs e) {
    _viewModel.ImageViewModel.RotationAngle = -90d;
    _viewModel.ImageViewModel.TranslateTransformX = -8.5;
    _viewModel.ImageViewModel.TranslateTransformY = 8.5;

    //It works, if uncomment next line, but violates MVVM
    //car.RenderTransform = _viewModel.ImageViewModel.TransformGroup;
}

Bounding doesn't work as it should and direct assigment violates MVVM pattern. What I should do?

UPD: MainWindow.xaml.cs:

public partial class MainWindow:Window {
    private readonly MainWindowViewModel _viewModel = new();

    public MainWindow() {
        InitializeComponent();
        DataContext = _viewModel;
    }

    // button click handler here...
}

Solution

  • All elements of a Binding Path must be properties. WPF data binding does not works with fields.

    This also applies to the ImageViewModel member of the MainWindowViewModel class:

    class MainWindowViewModel
    {
        public ImageViewModel ImageViewModel { get; } = new();
    }