1
votes

I've implemented checkboxes in my Xamarin Forms App using the following article:

https://alexdunn.org/2018/04/10/xamarin-tip-build-your-own-checkbox-in-xamarin-forms/

I'm trying to use the new BindableLayout to build a list of title's (Mr, Mrs etc):

<StackLayout x:Name="parent"
         Grid.Row="0"
         Grid.ColumnSpan="2"
         Orientation="Horizontal"
         BindableLayout.ItemsSource="{Binding Titles}">
<BindableLayout.ItemTemplate>
    <DataTemplate>
        <StackLayout Orientation="Horizontal">
            <control:CheckBoxView VerticalOptions="CenterAndExpand"
                                  IsChecked="..."
                                  CheckedCommand="{Binding BindingContext.CheckCommand, Source={x:Reference parent}}"
                                  CheckedCommandParameter="{Binding Identifier}"
                                  HorizontalOptions="Start"
                                  OutlineColor="{Binding BindingContext.Campaign.CampaignProfile.EntryBackgroundColor, Source={x:Reference parent}}"
                                  CheckedOutlineColor="{Binding BindingContext.Campaign.CampaignProfile.EntryTextColor, Source={x:Reference parent}}"
                                  CheckColor="{Binding BindingContext.Campaign.CampaignProfile.EntryTextColor, Source={x:Reference parent}}">
            </control:CheckBoxView>
            <Label Margin="0, 0, 20, 0"
                   VerticalOptions="Center"
                   VerticalTextAlignment="Center"
                   HorizontalTextAlignment="Start"
                   HorizontalOptions="FillAndExpand"
                   TextColor="{Binding BindingContext.Campaign.CampaignProfile.TextColor, Source={x:Reference parent}}"
                   FontSize="{Binding BindingContext.Campaign.CampaignProfile.TextSize, Source={x:Reference parent}}"
                   WidthRequest="150"
                   MinimumWidthRequest="100"
                   Text="{Binding Identifier}" />
            </StackLayout>
    </DataTemplate>
</BindableLayout.ItemTemplate>

The above code works almost as expected - I get a label and checkbox for each title in Titles. However I need a way to ensure that only one title is checked - this is what I can't get to work.

In the CheckCommand I set a property (SelectedTitle) to the Identifier of the set in CheckedCommandParameter - works fine, however I need some way to compare the value of Identifier and SelectedTitle.

I've been trying to get this working using an IValueConverter, however I can't bind a value to the CommandParameter, I've also tried DataTriggers, however that didn't work either.

Update:

This is with the DataTriggers - it feels like the CheckBoxView isn't setting the IsChecked property

<control:CheckBoxView VerticalOptions="CenterAndExpand"
                  IsChecked="False"
                  CheckedCommand="{Binding BindingContext.CheckCommand, Source={x:Reference parent}}"
                  CheckedCommandParameter="{Binding Identifier}"
                  HorizontalOptions="Start"
                  OutlineColor="{Binding BindingContext.Campaign.CampaignProfile.EntryBackgroundColor, Source={x:Reference parent}}"
                  CheckedOutlineColor="{Binding BindingContext.Campaign.CampaignProfile.EntryTextColor, Source={x:Reference parent}}"
                  CheckColor="{Binding BindingContext.Campaign.CampaignProfile.EntryTextColor, Source={x:Reference parent}}">
<control:CheckBoxView.Triggers>
    <DataTrigger TargetType="control:CheckBoxView"
                 Binding="{Binding BindingContext.SelectedTitle, Source={x:Reference parent}}"
                 Value="{Binding Identifier}">
        <Setter Property="IsChecked"
                Value="True"/>
    </DataTrigger>
</control:CheckBoxView.Triggers>

1
I'm not sure what the problem is? In CheckedCommand can't you just iterate through your model and clear IsChecked for any item that does NOT match the selected? It may help if you shared your CheckedCommand codeJason
@Jason I only have a single property to hold the selected value - I've updated the question with the DataTriggers option I've tried.markpirvine

1 Answers

1
votes

Over time I'd been adding more and more complexity to this, which is usually an indication you're going in the wrong direction. In the end this was solved using standard MVVM. I added a new class called Salutation:

public class Salutation : INotifyPropertyChanged
{
    private string identifier = null;
    private string name = null;
    private bool selected = false;

    public event PropertyChangedEventHandler PropertyChanged;

    [JsonProperty(PropertyName = "identifier", NullValueHandling = NullValueHandling.Ignore)]
    public string Identifier
    {
        get
        {
            return identifier
        }

        set
        {
            identifier = value;
            OnPropertyChanged();
        }
    }

    [JsonProperty(PropertyName = "name", NullValueHandling = NullValueHandling.Ignore)]
    public string Name
    {
        get
        {
            return name;
        }

        set
        {
            name = value;
            OnPropertyChanged();
        }
    }

    [JsonProperty(PropertyName = "selected", NullValueHandling = NullValueHandling.Ignore)]
    public bool Selected
    {
        get
        {
            return selected;
        }
        set
        {
            selected = value;
            OnPropertyChanged();
        }
    }

    public override int GetHashCode() => (Identifier).GetHashCode();

    public bool Equals(Salutation other) => (Identifier) == (other?.Identifier);

    public override string ToString()
    {
        return Identifier;
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Salutation item))
        {
            return false;
        }

        return Equals(item);
    }

    public static bool operator ==(Salutation salutation1, Salutation salutation2)
    {
        if (ReferenceEquals(salutation1, salutation2))
        {
            return true;
        }

        if (ReferenceEquals(salutation1, null) || ReferenceEquals(salutation2, null))
        {
            return false;
        }

        return salutation1.Equals(salutation2);
    }

    public static bool operator !=(Salutation salutation1, Salutation salutation2)
    {
        return !(salutation1 == salutation2);
    }

    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null) => 
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

I then added some Property and Collection changed event handlers:

Titles = new ObservableRangeCollection<Models.Salutation>();
Titles.CollectionChanged += Titles_CollectionChanged;
private bool handleEvent = true;

private void Titles_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        foreach (Models.Salutation item in e.NewItems)
            item.PropertyChanged += Salutation_PropertyChanged;

    if (e.OldItems != null)
        foreach (Models.Salutation item in e.OldItems)
            item.PropertyChanged -= Salutation_PropertyChanged;
}

private void Salutation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if(!handleEvent)
    {
        return;
    }

    if (e.PropertyName == "Selected")
    {
        handleEvent = false;

        if (sender is Models.Salutation selectedSalutation)
        {
            if (selectedSalutation.Selected)
            {
                Reset(sender as Models.Salutation);
            }
        }

        handleEvent = true;
    }
}

The XAML:

<control:CheckBoxView VerticalOptions="CenterAndExpand"
                      IsChecked="{Binding Selected, Mode=TwoWay}"
                      HorizontalOptions="Start"
                      OutlineColor="{Binding BindingContext.Campaign.CampaignProfile.EntryBackgroundColor, Source={x:Reference parent}}"
                      CheckedOutlineColor="{Binding BindingContext.Campaign.CampaignProfile.EntryTextColor, Source={x:Reference parent}}"
                      CheckColor="{Binding BindingContext.Campaign.CampaignProfile.EntryTextColor, Source={x:Reference parent}}">
</control:CheckBoxView>

Much simpler!