mshwf
mshwf

Reputation: 7449

Where does Binding expression get executed in Xamarin?

I'm creating a custom control which has an ItemsSource property:

    public static readonly BindableProperty ItemsSourceProperty = 
BindableProperty.Create("ItemsSource", typeof(IEnumerable<object>), typeof(RadioButtonsGroup), defaultBindingMode: BindingMode.TwoWay);

public IEnumerable<object> ItemsSource
    {
        get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
        set
        {
            SetValue(ItemsSourceProperty, value);
            OnItemsAdded(this, new ItemsAddedEventArgs(value));
        }
    }

I call the OnItemsAdded method in the property setter, to initialize the control, it get called only when I set the property like this:

myCustomControl.ItemsSource = vm.MyList;

but doesn't get called when I set it through data-binding:

<Controls:RadioButtonsGroup ItemsSource="{Binding MyList}" x:Name="myCustomControl"/>

so the control doesn't get the list and isn't initialized at all!

I don't want to use the propertyChanged delegate, because it's static and I need to use instance members in it.

Upvotes: 1

Views: 573

Answers (2)

Martin Zikmund
Martin Zikmund

Reputation: 39082

You need to notify the binding that the MyList property has changed. If you have the MyList property in the code-behind of the page (xaml.cs), it would look like this:

public class Detail : Page, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private List<int> _myList;
    public List<int> MyList
    {
       get => _myList;
       set 
       {
          _myList = value;
          NotifyPropertyChanged();
       } 
    }    
}

As you can see, you don't subscribe to the PropertyChanged event in the setter, but rather fire it. Behind the scenes the Binding subscribes to this event and when it is executed it updates the value of the property on your custom control.

To react to property changes, you need to add another parameter to the BindableProperty definition:

public static readonly BindableProperty ItemsSourceProperty = 
BindableProperty.Create("ItemsSource", ..., propertyChanged: OnItemsSourceChanged);

static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
    // Property changed implementation goes here
}

There is a nice tutorial on building Custom controls with bindable properties: https://mindofai.github.io/Creating-Custom-Controls-with-Bindable-Properties-in-Xamarin.Forms/

Upvotes: 0

Sir Rufo
Sir Rufo

Reputation: 19106

This is an example how you should implement a bindable property, that is a collection

public class RadioButtonsGroup : View
{
    public static BindableProperty ItemsSourceProperty = BindableProperty.Create(
        propertyName: nameof(ItemsSource),
        returnType: typeof(IEnumerable),
        declaringType: typeof(RadioButtonsGroup),
        defaultValue: null,
        defaultBindingMode: BindingMode.TwoWay,
        propertyChanged: OnItemsSourceChanged
    );

    public IEnumerable ItemsSource 
    { 
        get => (IEnumerable)GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty,value);
    }

    // gets called from BindableProperty 
    // whenever you assign a new value to ItemsSource property
    private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var @this = bindable as RadioButtonsGroup;

        // unsubscribe from the old value

        var oldNPC = oldValue as INotifyPropertyChanged;
        if (oldNPC != null)
        {
            oldNPC.PropertyChanged -= @this.OnItemsSourcePropertyChanged;
        }

        var oldNCC = oldValue as INotifyCollectionChanged;
        if (oldNCC != null)
        {
            oldNCC.CollectionChanged -= @this.OnItemsSourceCollectionChanged;
        }

        // subscribe to the new value

        var newNPC = newValue as INotifyPropertyChanged;
        if (newNPC != null)
        {
            newNPC.PropertyChanged += @this.OnItemsSourcePropertyChanged;
        }

        var newNCC = newValue as INotifyCollectionChanged;
        if (newNCC != null)
        {
            newNCC.CollectionChanged += @this.OnItemsSourceCollectionChanged;
        }

        // inform the instance to do something

        @this.RebuildOnItemsSource();
    }

    private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // handle the collection changes
        throw new NotImplementedException();
    }

    private void OnItemsSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // handle the property changes
        throw new NotImplementedException();
    }

    private void RebuildOnItemsSource()
    {
        if (ItemsSource == null)
        {
            // clear out all
        }
        else
        {
            // complete creation of all subviews
        }
    }
}

Upvotes: 2

Related Questions