Disable a button with command in Xamarin.Forms

November 27th 2020 Xamarin

The Button view has an IsEnabled property that can be used to disable it. However, when a command is bound to it, the button is initially enabled no matter the value of the IsEnabled property. This can introduce a subtle bug that's not always easy to notice.

Imagine the following snippet from a login page:

<Entry Placeholder="Username"
       Text="{Binding Username}" />
<Entry Placeholder="Password"
       Text="{Binding Password}"
       IsPassword="True"/>
<Button VerticalOptions="Center"
        Text="Login"
        IsEnabled="{Binding LoginAllowed}"
        Command="{Binding LoginCommand}"/>

The view model code disables the button when at least one input field is empty:

public LoginViewModel()
{
    LoginCommand = new Command(OnLoginClicked);
}

public string Username
{
    get => username;
    set
    {
        SetProperty(ref username, value);
        OnPropertyChanged(nameof(LoginAllowed));
    }
}

public string Password
{
    get => password;
    set
    {
        SetProperty(ref password, value);
        OnPropertyChanged(nameof(LoginAllowed));
    }
}

public bool LoginAllowed =>
    !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password);

Surprisingly enough, when the page loads, the button is enabled, although both input fields are empty. It gets disabled though when either field is modified. This means that the view model code is correct. The view just doesn't initialize correctly.

The fix is even more interesting. You only need to put the IsEnabled attribute in the XAML markup after the Command attribute:

<Button VerticalOptions="Center"
        Text="Login"
        Command="{Binding LoginCommand}"
        IsEnabled="{Binding LoginAllowed}"/>

It's not a difficult fix. But you might forget to do it or have a problem remembering which order is the correct one.

For the latter, it might be helpful to keep in mind that the command has its own means for being enabled or disabled - the CanExecute method and the CanExecuteChanged event. If the command is placed later in the markup, these could override the IsEnabled attribute. That could even be the real reason behind this strange behavior.

To avoid the issue altogether, you can use the command to enable and disable the button instead of the IsEnabled attribute. Not much code needs to be changed for that:

public LoginViewModel()
{
    LoginCommand = new Command(OnLoginClicked, LoginAllowed);
}

public string Username
{
    get => username;
    set
    {
        SetProperty(ref username, value);
        LoginCommand.ChangeCanExecute();
    }
}

public string Password
{
    get => password;
    set
    {
        SetProperty(ref password, value);
        LoginCommand.ChangeCanExecute();
    }
}

public bool LoginAllowed(object obj) =>
    !string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password);

Let's walk through the changes:

  1. The LoginAllowed property is converted into a method. Thanks to expression-bodied members, the syntax is very similar.
  2. The new LoginAllowed method is passed as the second argument to the command constructor.
  3. The Username and Password properties use the ChangeCanExecute method to raise the CanExecuteChanged event on the command instead of the PropertyChanged event on the view model.

Now, only the command needs to be bound to the button in markup:

<Button VerticalOptions="Center"
        Text="Login"
        Command="{Binding LoginCommand}"/>

You can check a working sample in my GitHub repository. Individual commits contain all three approaches: the broken one and the two working ones.

If you're binding both the Command and the IsEnabled property to a button, make sure that you place IsEnabled after Command for this to work properly. Alternatively, implement CanExecute on the command so that there's no need for IsEnabled anymore.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License