Binding to ICommand with Xamarin Forms

Alternate title

Why a Xamarin forms Button bound to ICommand doesn’t enable or disable automtically via CanExecute

So I’ve been pretty excited about Xamarin Forms being implemented with Xaml. I recently decided to take time to implement binding using MVVM and just see how it works.

I used to use a common object called RelayCommand that implemented the ICommand interface. However, there is no need for this object in Xamarin Forms, because Xamarin Forms has Command and Command<T> that both implement this.

So in WPF, I have an example for ICommand. It used both

<UserControl x:Class="RelayCommandExample.View.HelloWorldView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d"
             d:DesignHeight="200"
             d:DesignWidth="300"
             >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0">
            <TextBlock Text="{Binding Text}" />
        </Label>
        <Button Grid.Row="1" Content="{Binding Button1.Text}" Command="{Binding Button1.Command}"/>
    </Grid>
</UserControl>

I have some simple view model properties that this view binds to.

	public String Text
	{
		get { return _Text; }
		set { SetProperty(ref _Text, value); }
	} private string _Text;

	public ButtonViewModel Button1
	{
		get
		{
			return _Button1 ?? (_Button1 = new ButtonViewModel
			{
				Text = "Click me!",
				Command = new RelayCommand(f => Button1Clicked(), f => Button1CanClick())
			});
		}
	} private ButtonViewModel _Button1;        

	private void Button1Clicked()
	{
		Text = "Button clicked " + ++_ClickCount + " times.";
	} private int _ClickCount;

	private bool Button1CanClick()
	{
		return _ClickCount < 10;
	}

I also used a ButtonViewModel in the above code. Note: My ButtonViewModel is far more advanced than this, but for this example, it has been stripped down to only the Button.Text and Button.Command.

    public class ButtonViewModel : ViewModelBase
    {
        public string Text
        {
            get { return _Text; }
            set { SetProperty(ref _Text, value); }
        } private string _Text;

        public RelayCommand Command
        {
            get { return _Command; }
            set { SetProperty(ref _Command, value); }
        } RelayCommand _Command;
    }

As you see, Button1CanClick() returns true for the first ten clicks, then returns false. That means the button should be enabled to start, but it should automatically disable after the tenth click. This works perfectly in WPF.

However, when I ported this code over to Xamarin Forms, I had a few problems.

CommandManager.RequerySuggested missing

The project didn’t build because RelayCommand references CommandManager.RequerySuggested which doesn’t work because it is part of PresentationCore, a library which doesn’t exist in Xamarin Forms.
Resolution: I replaced RelayCommand with Command from Xamarin Forms.

    public ButtonViewModel Button1
    {
        get
        {
            return _Button1 ?? (_Button1 = new ButtonViewModel
            {
                Text = "Click me!",
                Command = new Command(f => Button1Clicked(), f => Button1CanClick())
            });
        }
    } private ButtonViewModel _Button1;
    public class ButtonViewModel : ViewModelBase
    {
        public string Text
        {
            get { return _Text; }
            set { SetProperty(ref _Text, value); }
        } private string _Text;

        public Command Command
        {
            get { return _Command; }
            set { SetProperty(ref _Command, value); }
        } Command _Command;
    }

The project built perfectly and ran after that.

CanExecute method is not called after each click

The button didn’t disabled after 10 clicks. In fact, the CanExecute method was only called once at first load. I expected it to be called by any number of events, but it was never called. In WPF, it is CommandManager.RequerySuggested that makes this work, so with that code not existing, the CanExecuteChanged event must be fired manually.

Well, it seems that a simple way to do this would be to subscribe to the PropertyChanged event. So I deviced to bring back RelayCommand only this time inherit from Command.

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace MVVM
{
    public class RelayCommand : Command
    {
        public RelayCommand(Action<object> execute)
            : base(execute)
        {
        }

        public RelayCommand(Action execute)
            : this(o => execute())
        {
        }

        public RelayCommand(Action<object> execute, Func<object, bool> canExecute, INotifyPropertyChanged npc = null)
            : base(execute, canExecute)
        {
            if (npc != null)
                npc.PropertyChanged += delegate { ChangeCanExecute(); };
        }

        public RelayCommand(Action execute, Func<bool> canExecute, INotifyPropertyChanged npc = null)
            : this(o => execute(), o => canExecute(), npc)
        {
        }
    }
}

Now I can go back to using RelayCommand only add a parameter to pass the host object, using the keyword this.

    public ButtonViewModel Button1
    {
        get
        {
            return _Button1 ?? (_Button1 = new ButtonViewModel
            {
                Text = "Click me!",
                Command = new Command(f => Button1Clicked(), f => Button1CanClick(), this)
            });
        }
    } private ButtonViewModel _Button1;

So every time the PropertyChanged event fires, CanExecute is called. Note that for production, you may have to do checks outside of when the PropertyChanged event fires depending on what you are evaluating in CanExecute.

Button disabled by CanExecute

It works in Android and iOS as well.

Xamarin Forms Conclusion

I have no complaints with the technology. XAML and Binding and MVVM appear to be working quite well. All the benefits declarative UI and MVVM layers, combined with the ability to code for all mobile platforms in one place, in one language. It would be too hard to create full desktop app in WPF either as the ViewModel is reuseable. Xamarin also supports Mac OSX. So really you are looking at the future go to tool for building cross platform applications that aren’t web-based.

Leave a Reply