Search code examples
androidiosxamarin.formsmvvmcross

CAN NOT get my button's IsEnabled state to change


I have tried umpteen-ways-to-Sunday to get this to work, but I'm obviously missing something because my "Register" button is not getting enabled no matter what I do.

In my XAML:

<Button Text="Register" 
        Style="{StaticResource RegularButtonStyle}" 
        WidthRequest="280"
        x:Name="RegisterButton">
    <!-- I HAD the following, but it wasn't working so I finally decided to try DataTriggers -->
    <!-- bindings:Bi.nd="Clicked RegisterButtonClickedCommand;IsEnabled IsRegisterButtonEnabled"> -->
    <Button.Triggers>
        <DataTrigger TargetType="Button"
                     Binding="{Binding IsRegisterButtonEnabled}"
                     Value="False">
            <Setter Property="IsEnabled" Value="False"></Setter>
        </DataTrigger>
        <DataTrigger TargetType="Button"
                     Binding="{Binding IsRegisterButtonEnabled}"
                     Value="True">
            <Setter Property="IsEnabled" Value="True"></Setter>
        </DataTrigger>
    </Button.Triggers>
</Button>

In the ViewModel:

public class RegisterViewModel : MvxViewModel
{
    ...

    private bool _isRegisterButtonEnabled;
    public bool IsRegisterButtonEnabled
    {
        get => ShouldEnableRegisterButton();

        set
        {
            _isRegisterButtonEnabled = value;
            SetProperty(ref _isRegisterButtonEnabled, value);
        }
    }

    ...
    public IMvxCommand RegisterButtonClickedCommand { get; private set; }
    ...

    private void InitializeCommands()
    {
        ...
        RegisterButtonClickedCommand = new MvxCommand(RegisterUser);
    }

    ...

    private bool ShouldEnableRegisterButton()
    {
        var isValidUser = _userName.Validate();
        var isValidPass = _password.Validate();
        var isValidConfirmedPass = _confirmedPassword.Validate();

        var shouldEnable = isValidUser && isValidPass && isValidConfirmedPass;

        _mvxLogger.Log(MvxLogLevel.Trace, () => $"RegisterViewModel : ShouldEnableRegisterButton() called. Returning: {shouldEnable}");

        return shouldEnable;
    }

    ...
}

I've read the MvvmCross "Documentation" but it's all conversational and I can't find any SPECIFIC examples of binding to a button's IsEnabled property with enough specificity to get me there.

Sure would appreciate some help. :)

Housekeeping information: I was using the following NuGet packages (/libraries)
  • MvvmCross & MvvmCross.Forms v6.3.1
  • Xamarin.Forms v4.0.0.497661
  • Xamarin.Android.* libs are all v28.0.0.1
Then thought that I should update, since there are updates available, so I updated to the following;
  • MvvmCross & MvvmCross.Forms v6.4.1
  • Xamarin.Forms v4.2.0.848062
  • Xamarin.Android.* libs are all now v28.0.0.3

Also using .Net Standard v2.0.3 for the Shared stuff.


Solution

  • The Final Answer turned out to be a combination of issues, and one big one that will probably require a new post/question here on SO.

    So here's what ended up working;

    I updated the Xaml to the following;

    <Button Text="Register" 
            Style="{StaticResource RegularButtonStyle}" 
            WidthRequest="280"
            x:Name="RegisterButton"
            Command="{Binding RegisterButtonClickedCommand}">
        <Button.Triggers>
            <DataTrigger TargetType="Button"
                         Binding="{Binding IsRegisterButtonEnabled}"
                         Value="False">
                <Setter Property="IsEnabled" Value="False"></Setter>
            </DataTrigger>
            <DataTrigger TargetType="Button"
                         Binding="{Binding IsRegisterButtonEnabled}"
                         Value="True">
                <Setter Property="IsEnabled" Value="True"></Setter>
            </DataTrigger>
        </Button.Triggers>
    </Button>
    

    Also updated RegisterViewModel.cs to the following;

    public class RegisterViewModel : MvxViewModel, INotifyPropertyChanged
    {
        ...
    
        // Added this (and the inheriting from INotify.. [per @Adlorem]
        public event PropertyChangedEventHandler PropertyChanged;
    
        private bool _isRegisterButtonEnabled;
        public bool IsRegisterButtonEnabled
        {
            get
            {
                _mvxLogger.Log(MvxLogLevel.Trace,
                    () => $"RegisterViewModel : IsRegisterButtonEnabled property get called. Returning: {_isRegisterButtonEnabled}");
    
                return _isRegisterButtonEnabled;
            } 
    
            set
            {
                _isRegisterButtonEnabled = value;
    
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsRegisterButtonEnabled)));
    
                _mvxLogger.Log(MvxLogLevel.Trace,
                    () => $"RegisterViewModel : IsRegisterButtonEnabled property set called. value: {value}");
    
            }
        }
    
        ...
    
        private bool ShouldEnableRegisterButton()
        {
            var isValidUser = _userName.Validate();
            var isValidPass = _password.Validate();
            var isValidConfirmedPass = _confirmedPassword.Validate();
    
            var shouldEnable = isValidUser && isValidPass && isValidConfirmedPass;
    
            // Must set this or the PropertyChanged definitely won't fire!
            IsRegisterButtonEnabled = shouldEnable;
    
            _mvxLogger.Log(MvxLogLevel.Trace,
                () => $"RegisterViewModel : ShouldEnableRegisterButton() called. Returning: {shouldEnable}");
    
            return shouldEnable;
        }
    
        ...
    }
    

    Obviously this is not optimal. MvvmCross's SetProperty() should be firing the PropertyChanged event and for some reason it doesn't.