7

I have a canvas, e.g. similar to this solution or many others using the ItemsControl.

Now I want a button which should be bound to an ICommand. This command should call a method of ViewModel class which can save the image. The saving method is clear, but how do I do the binding following the MVVM pattern?

Community
  • 1
  • 1
Rolfi
  • 452
  • 5
  • 13

2 Answers2

10

You could pass the Canvas to the ViewModel's Save method using a CommandParameter

<Button Content="Save" 
        Command="{Binding SaveCanvasCommand}" 
        CommandParameter="{Binding ElenementName=myCanvas}" ?>

<Canvas x:Name="myCanvas">
   <!-- Stuff to save -->
</Canvas>

And somewhere in you ViewModel or Command you'd have

void SaveCanvasCommandExecute(object parameter)
{
    UIElement toSave = (UIElement)parameter;
    //.. You'd probably use RenderTargetBitmap here to save toSave.
}
Jens
  • 25,229
  • 9
  • 75
  • 117
  • Not at all a WPF guy, but doesnt your code violate MVVM principle that a UI element shouldnt be accessed inside a VM? – nawfal May 17 '20 at 04:28
2

If you don't want to reference UI elements in your ViewModel you could use an attached behaviour:

internal static class Behaviours
{
    public static readonly DependencyProperty SaveCanvasProperty =
        DependencyProperty.RegisterAttached("SaveCanvas", typeof(bool), typeof(Behaviours),
                                            new UIPropertyMetadata(false, OnSaveCanvas));

    public static void SetSaveCanvas(DependencyObject obj, bool value)
    {
        obj.SetValue(SaveCanvasProperty, value);
    }

    public static bool GetSaveCanvas(DependencyObject obj)
    {
        return (bool)obj.GetValue(SaveCanvasProperty);
    }

    private static void OnSaveCanvas(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
        {
            // Save code.....
        }
    }
}

Then in your ViewModel you have your Command that sets a property, also on your ViewModel:

    public ICommand SaveCanvasCommand
    {
        get
        {
            if (_saveCanvasCommand == null)
                _saveCanvasCommand = new RelayCommand(() => { IsSaveCanvas = true; });

            return _saveCanvasCommand;
        }
    }

And the property which is bound to your View:

    public bool IsSaveCanvas
    {
        get { return _isSaveCanvas; }
        set
        {
            _isSaveCanvas = value;
            RaisePropertyChanged("IsSaveCanvas");
        }
    }

Then hooking it all up in the Xaml looks like this:

Add a Trigger on the Control that binds the value of your ViewModel property to your attached behaviour:

<UserControl.Style>
    <Style>
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsSaveCanvas}" Value="True">
                <Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="True"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding IsSaveCanvas}" Value="False">
                <Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="False"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</UserControl.Style>

And then bind your Button / MenuItem to the ViewModels Save Command:

    <Canvas.ContextMenu>
        <MenuItem Header="Save" Command="{Binding SaveCanvasCommand}"/>
    </Canvas.ContextMenu>
Richard E
  • 4,819
  • 1
  • 19
  • 25