Multitrigger - Disable button based on conditions

Hi all,

I'm trying to get a button to only be enabled if the required fields are completed. In this instance these 2 fields are a Picker for expense type and an expense value.

I've written a behaviour for the expense value like this:

public class ExpenseValueBehaviour : Behavior<Entry>
    {
        public ExpenseValueBehaviour()
        {

        }

        const string valueRegex = @"^(\d+.\d{2})$|^(\d+)$";

        static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(ExpenseValueBehaviour), false);
        public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;

        public bool IsValid
        {
            get { return (bool)base.GetValue(IsValidProperty); }
            private set { base.SetValue(IsValidPropertyKey, value);}
        }

        protected override void OnAttachedTo(Entry bindable)
        {
            bindable.TextChanged += HandleTextChanged;
        }

        void HandleTextChanged(object sender, TextChangedEventArgs e)
        {
            IsValid = (Regex.IsMatch(e.NewTextValue, valueRegex, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
            ((Entry)sender).TextColor = IsValid? Color.Default : Color.Red;
        }

        protected override void OnDetachingFrom(Entry bindable)
        {
            bindable.TextChanged -= HandleTextChanged;
        }
    }

And I have a NotNull converter for checking if the select item for the picker is null like this:

    public class IsNotNullConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null)
                return true;
            return false;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
            return null;            
        }
    }

These both appear to be working correctly, break points on the NotNull converter confirm this.
Then within my xaml:

<ContentPage.Resources>
        <ResourceDictionary>
            <StudyWise:NegateBooleanConverter x:Key="inverter"/>
            <StudyWise:IsNotNullConverter x:Key="notNull"/>
        </ResourceDictionary>
    </ContentPage.Resources>

.... (CODE REMOVED FOR BREVITY)
    <Picker x:Name="typePicker" Title="Select type" Grid.Row="0" Grid.Column="1" VerticalOptions="CenterAndExpand" 
                        ItemsSource="{Binding ExpenseTypes}" ItemDisplayBinding="{Binding Description}" SelectedItem="{Binding ExpenseType}">
                    </Picker>

                    <Label Text="Cost" Grid.Row="1" Grid.Column="0" VerticalOptions="CenterAndExpand"/>
                    <Entry Keyboard="Numeric" Text="{Binding EstimatedCost}" Grid.Row="1" Grid.Column="1" VerticalOptions="CenterAndExpand">
                        <Entry.Behaviors>
                          <StudyWise:ExpenseValueBehaviour x:Name="valueValidator"/>
                        </Entry.Behaviors>
                    </Entry>

I then have these triggers on the button:

<Button Text="Create" Command="{Binding CreateExpenseCommand}" IsEnabled="false">
                    <Button.Style>
                        <Style TargetType="Button">
                            <Style.Triggers>
                                <MultiTrigger TargetType="Button">
                                    <MultiTrigger.Conditions>
                                        <BindingCondition Binding="{Binding Source={x:Reference valueValidator}, Path=IsValid}" Value="True" />
                                        <BindingCondition Binding="{Binding Source={x:Reference typePicker}, Path=SelectedItemProperty, Converter={StaticResource notNull}}" Value="True" />
                                    </MultiTrigger.Conditions>
                                    <Setter Property="IsEnabled" Value="True" />
                                </MultiTrigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>
                </Button>

The problem is that my button is always enabled still. I know that the valueValidator is getting called and the notNull converter is by inserting break points in the relevant classes.

Not sure if it makes any difference but I'm using FreshMVVM and running Xamarin 2.3.4.192-pre2

Many thanks in advance

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited March 2017

    Usually we use the CanExecute feature for a button with a command.
    Its part of binding. The button will enable/disable itself based on the result of the CanExecute evaluation. This way you're not micromanaging the controls. If you had 10 controls that were all wired to the same commmand, they would all disable when the CanExecute is false.

    https://blog.xamarin.com/simplifying-events-with-commanding/
    https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/

    NOTE: There is a bug when it comes to Xamarin binding: If the object of the binding evaluates to null no further processing is done and the result is always returned as true.
    https://bugzilla.xamarin.com/show_bug.cgi?id=46603

    I suspect this might be the source of your issue. It looks like you're trying to bind against something that may or may not be null:

    Path=SelectedItemProperty, Converter={StaticResource notNull}}

    It won't work. If the item is null the converter will never get called, won't do its evaluation and won't return anything. The result of this binding will always be true when null, and I'll bet the converter returns true when there is a value... so the end result is you always get a true out of this. If you read through the comments on that bugzilla I reported it for nearly the same type of use: I wanted the button to be disabled when a conditional object was null.

    Hey @DavidOrtinau ... Could you weigh in on this please? Back in February you said you were going to look in on this particular bugzilla along with 5 others I reported. They were months-old back in February and I've seen no movement on them still; as another month closes. Is there anything being done on this very basic functionality bug?

Sign In or Register to comment.