Have you ever used some control in Xamarin.Forms that appears to have an event instead of a command? You are working the MVVM love and then you come across that annoying control. Almost all the Xamarin.Forms controls have both events and commands, enabling choice, but you do get those non-conforming ones…
I was lurking on the Xamarin forums and I came across a question regarding the event-only design of the SkiaSharp SKCanvasView
. The SKCanvasView
has a PaintSurface
event that allows you to draw on the view with SkiaSharp commands.
There is nothing wrong with the event as it stands, but things get messy when all the data is in a view model. In simple cases, this is easy to resolve. Take a small example where there is only one view model, which is the binding context for an entire Page
:
public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); BindingContext = new MainViewModel(); } }
And the view model looks like this:
public class MainViewModel { public MainViewModel() { PaintCommand = new Command<SKPaintSurfaceEventArgs>(OnPainting); } public ICommand PaintCommand { get; private set; } private void OnPainting(SKPaintSurfaceEventArgs e) { // ... draw ... } }
Where, or rather how, does one get an event somewhere on the page to the view model? Well, one way would be to attach a handler to the view and then pass the event arguments to the view model:
canvasView.PaintSurface += (sender, e) => { // we can do this because of our simple example var viewModel = (MainViewModel)BindingContext; // fire the command if (viewModel.PaintCommand.CanExecute(e)) { viewModel.PaintCommand.Execute(e); } };
This will work as everything is pretty much straight-forward. However, what happens if the canvas is in a ListView
or if there are several canvases on the page? We would end up with a messy code-behind and probably a few memory leaks.
A much better way to solve this problem would be to use Xamarin.Forms’ behaviors:
Behaviors lets you add functionality to user interface controls without having to subclass them. Behaviors are written in code and added to controls in XAML or code.
Behaviors are easy to create and easy to use. The first thing we need to do is create our specific behavior:
public class PaintSurfaceCommandBehavior : Behavior<SKCanvasView> { // we need a bindable property for the command public static readonly BindableProperty CommandProperty = BindableProperty.Create( nameof(Command), typeof(ICommand), typeof(PaintSurfaceCommandBehavior), null); // the command property public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } // invoked immediately after the behavior is attached to a control protected override void OnAttachedTo(SKCanvasView bindable) { base.OnAttachedTo(bindable); // we want to be notified when the view's context changes bindable.BindingContextChanged += OnBindingContextChanged; // we are interested in the paint event bindable.PaintSurface += OnPaintSurface; } // invoked when the behavior is removed from the control protected override void OnDetachingFrom(SKCanvasView bindable) { base.OnDetachingFrom(bindable); // unsubscribe from all events bindable.BindingContextChanged -= OnBindingContextChanged; bindable.PaintSurface -= OnPaintSurface; } // the view's context changed private void OnBindingContextChanged(object sender, EventArgs e) { // update the behavior's context to match the view BindingContext = ((BindableObject)sender).BindingContext; } // the canvas needs to be painted private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) { // first check if the command can/should be fired if (Command?.CanExecute(e) == true) { // fire the command Command.Execute(e); } } }
Now that we have the behavior, we can simply add it to the canvas view:
<views:SKCanvasView> <views:SKCanvasView.Behaviors> <local:PaintSurfaceCommandBehavior Command="{Binding PaintCommand}" /> </views:SKCanvasView.Behaviors> </views:SKCanvasView>
That’s it! Our canvas is now command-based and can be drawn on from the view model.
This is just a very limited behavior, for one specific event for one specific view. But, behaviors are very powerful and can be used to do many more things. One such use is to make a more generic behavior that can map any event to any command:
- Xamarin Docs – Reusable EventToCommandBehavior
- Xamarin Blog – Turn Events into Commands with Behaviors
- Anthony Simmon – EventToCommand in Xamarin Forms Apps
Besides those examples, be sure to read more about behaviors, and what they can do, in the Xamarin documentation.
added Parameter to XAML
(so i can use to show ressource directly)
———–
PaintSurfaceCommandBehavior:
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(
nameof(CommandParameter),
typeof(object),
typeof(PaintSurfaceCommandBehavior),
null);
// the command property
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
………
SurfaceEventArgsWithParameter eaParam = new SurfaceEventArgsWithParameter() { eventArgs = e, ResourceName = (string)CommandParameter };
Command.Execute(eaParam);
………..
public class SurfaceEventArgsWithParameter
{
public SKPaintSurfaceEventArgs eventArgs { get; set; }
public string ResourceName { get; set; }
}
—————–
XAML:
—————–
LikeLike
ups xaml filtered out:
[ss:SKCanvasView x:Name=”canvas” WidthRequest=”300″ HeightRequest=”300″ ]
[ss:SKCanvasView.Behaviors]
[helper:PaintSurfaceCommandBehavior
Command=”{Binding PaintCommand}”
CommandParameter=”c.svg”
/]
[/ss:SKCanvasView.Behaviors]
[/ss:SKCanvasView]
LikeLike
Very helpful post, was struggling with how to use Skiasharp in an MVVM based XF app, this really pointed me in the right direction. Thanks!
LikeLike