Disable a button with command in Xamarin.Forms
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:
- The
LoginAllowed
property is converted into a method. Thanks to expression-bodied members, the syntax is very similar. - The new
LoginAllowed
method is passed as the second argument to the command constructor. - The
Username
andPassword
properties use theChangeCanExecute
method to raise theCanExecuteChanged
event on the command instead of thePropertyChanged
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.