locked
Xamarin Forms - CustomObject, XAML Initialization, Setter not called, DependencyProperty, I'm lost ! RRS feed

  • Question

  • User211233 posted

    Hi,

    I have a problem and I searched a solution about it. Lucky, I red lot of post about it but I'm lost with the explaination I found. The initale problem is coming from a personal project about the polyline of the Xamarin.Forms.Map where the initialization is realized by a binding from the XAML part..

    Let me be clear by an example :

    I have an object CustomMap.cs which inherit from Xamarin.Forms.Map (This file is in the PCL part -> CustomControl/CustomMap.cs)

    public class CustomMap : Map, INotifyPropertyChanged
    {
        public static readonly BindableProperty PolylineAddressPointsProperty =
            BindableProperty.Create(nameof(PolylineAddressPoints), typeof(List<string>), typeof(CustomMap), null);
        public List<string> PolylineAddressPoints
        {
            get { return (List<string>)GetValue(PolylineAddressPointsProperty); }
            set
            {
                SetValue(PolylineAddressPointsProperty, value);
                this.GeneratePolylineCoordinatesInner();
            }
        }   
        // ...
    }
    

    So the Xaml part of the page, where the control is called, looks like that:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:control="clr-namespace:MapPolylineProject.CustomControl;assembly=MapPolylineProject"
             x:Class="MapPolylineProject.Page.MainPage">
    
      <ContentPage.Content>
        <control:CustomMap x:Name="MapTest" PolylineAddressPoints="{Binding AddressPointList}"
                                           VerticalOptions="Fill" HorizontalOptions="Fill"/>
    
      </ContentPage.Content>
    
    </ContentPage>
    

    The Csharp part:

    public partial class MainPage : ContentPage
    {
        public List<string> AddressPointList { get; set; }
    
        public MainPage()
        {
            base.BindingContext = this;
    
            AddressPointList = new List<string>()
            {
                "72230 Ruaudin, France",
                "72100 Le Mans, France",
                "77500 Chelles, France"
            };
    
            InitializeComponent();
    
            //MapTest.PolylineAddressPoints = AddressPointList;
        }
    }
    

    So, everything is fine if I edit the PolylineAddressPoints from the object instance (if the commented part isnt' commented..), but if I init the value from the XAML (from the InitializeComponent();), it doesn't work, the SetValue, in the Set {}, isn't called..

    I then searched on the web about it and get something about the Dependency Properties? or something like that. So I tried some solutions but, from WPF, so some methods, such as DependencyProperty.Register();. So yeah, I can't find the way to solve my problem..

    I also though about something, if DependencyProperty.Register(); would exists in Xamarin.Forms, then it means I would have to do it for each values? Because, if every value has to be set by a XAML binding logic, it would not work, I would have to register every value, doesn't it?

    I'm sorry if I'm not clear, but I'm so lost about this problem.. Please, do not hesitate to ask for more details, thank in advance !

    Sunday, September 11, 2016 5:24 PM

Answers

  • User211233 posted

    Hi,

    I finaly got a solution just over here => http://stackoverflow.com/questions/39491978/ignore-the-binding-initialization/39506043#39506043

    Copy paste from Stackoverflow. This following answer was given by Stephane Delcroix, thank to him !

    There are multiple questions in this:

    • Why is the property setter never called when using Xaml ?
    • Am I properly defining my BindableProperty ?
    • Why is my binding failing ?

    Let me answer them in a different order.

    Am I properly defining my BindableProperty ?

    The BindableProperty declaration is right, but could be improved by using an IList<string>:

    public static readonly BindableProperty PolylineAddressPointsProperty =
        BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null);
    

    but the property accessor is wrong, and should only contains this:

    public IList<string> PolylineAddressPoints
    {
        get { return (IList<string>)GetValue(PolylineAddressPointsProperty); }
        set { SetValue(PolylineAddressPointsProperty, value); }
    }
    

    I'll tell you why while answering the next question. But you want to invoke a method when the property has changed. In order to do that, you have to reference a propertyChanged delegate to CreateBindableProperty, like this:

    public static readonly BindableProperty PolylineAddressPointsProperty =
        BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null,
                                propertyChanged: OnPolyLineAddressPointsPropertyChanged);
    

    And you have to declare that method too:

    static void OnPolyLineAddressPointsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((CustomMap)bindable).OnPolyLineAddressPointsPropertyChanged((IList<string>)oldValue, (IList<string>)newValue);
    }
    
    void OnPolyLineAddressPointsPropertyChanged(IList<string> oldValue, IList<string> newValue)
    {
        GeneratePolylineCoordinatesInner();
    }
    

    Why is the property setter never called when using Xaml ?

    The property, and the property accessors, are only meant to be invoked when accessing the property by code. C# code.

    When setting a property with a BindablePrperty backing store from Xaml, the property accessors are bypassed and SetValue() is used directly.

    When defining a Binding, both from code or from Xaml, property accessors are again bypassed and SetValue() is used when the property needs to be modified. And when SetValue() is invoked, the propertyChanged delegate is executed after the property has changed (to be complete here, propertyChanging is invoked before the property change).

    You might wonder why bother defining the property if the bindable property is only used by xaml, or used in the context of Binding. Well, I said the property accessors weren't invoked, but they are used in the context of Xaml and XamlC:

    • a [TypeConverter] attribute can be defined on the property, and will be used
    • with XamlC on, the property signature can be used to infer, at compile time, the Type of the BindableProperty.

    So it's a good habit to always declare property accessors for public BindableProperties. ALWAYS.

    Why is my binding failing ?

    As you're using CustomMap as bot View and ViewModel (I won't tell the Mvvm Police), doing this in your constructor should be enough:

    BindingContext = this; //no need to prefix it with base.
    

    As you're doing it already, your Binding should work once you've modified the BindableProperty declaration in the way I explained earlier.

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Monday, September 19, 2016 1:13 PM

All replies

  • User226261 posted

    @Emixam23 juts copy your InitializeComponent(); method in first line of MainPage () constructor.

    Sunday, September 11, 2016 10:47 PM
  • User211233 posted
       public List<string> AddressPointList { get; set; }
    
        public MainPage()
        {
            base.BindingContext = this;
            InitializeComponent();
    
            AddressPointList = new List<string>()
            {
                "72230 Ruaudin, France",
                "72100 Le Mans, France",
                "77500 Chelles, France"
            };
    
            Debug.WriteLine("HALLOOOO");
    
            foreach (string addr in MapTest.PolylineAddressPoints)
            {
                Debug.WriteLine("addr => {0}", addr);
            }
            //MapTest.PolylineAddressPoints = AddressPointList;
        }
    

    =>

    HALLOOOO
    Exception thrown: 'System.NullReferenceException' in MapPolylineProject.dll
    The program '[0x3A94] MapPolylineProject.UWP.exe' has exited with code -1 (0xffffffff).
    

    Thank for your help !

    Sunday, September 11, 2016 11:36 PM
  • User226261 posted

    @Emixam23 no i mean InitializeComponent(); should be the first line. it should call before rest.

    Monday, September 12, 2016 1:37 AM
  • User211233 posted

    I tried to put it before the base context but it gives me the same thing.. can you put an example please?

    Monday, September 12, 2016 10:44 AM
  • User211233 posted

    Hi,

    I finaly got a solution just over here => http://stackoverflow.com/questions/39491978/ignore-the-binding-initialization/39506043#39506043

    Copy paste from Stackoverflow. This following answer was given by Stephane Delcroix, thank to him !

    There are multiple questions in this:

    • Why is the property setter never called when using Xaml ?
    • Am I properly defining my BindableProperty ?
    • Why is my binding failing ?

    Let me answer them in a different order.

    Am I properly defining my BindableProperty ?

    The BindableProperty declaration is right, but could be improved by using an IList<string>:

    public static readonly BindableProperty PolylineAddressPointsProperty =
        BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null);
    

    but the property accessor is wrong, and should only contains this:

    public IList<string> PolylineAddressPoints
    {
        get { return (IList<string>)GetValue(PolylineAddressPointsProperty); }
        set { SetValue(PolylineAddressPointsProperty, value); }
    }
    

    I'll tell you why while answering the next question. But you want to invoke a method when the property has changed. In order to do that, you have to reference a propertyChanged delegate to CreateBindableProperty, like this:

    public static readonly BindableProperty PolylineAddressPointsProperty =
        BindableProperty.Create(nameof(PolylineAddressPoints), typeof(IList<string>), typeof(CustomMap), null,
                                propertyChanged: OnPolyLineAddressPointsPropertyChanged);
    

    And you have to declare that method too:

    static void OnPolyLineAddressPointsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((CustomMap)bindable).OnPolyLineAddressPointsPropertyChanged((IList<string>)oldValue, (IList<string>)newValue);
    }
    
    void OnPolyLineAddressPointsPropertyChanged(IList<string> oldValue, IList<string> newValue)
    {
        GeneratePolylineCoordinatesInner();
    }
    

    Why is the property setter never called when using Xaml ?

    The property, and the property accessors, are only meant to be invoked when accessing the property by code. C# code.

    When setting a property with a BindablePrperty backing store from Xaml, the property accessors are bypassed and SetValue() is used directly.

    When defining a Binding, both from code or from Xaml, property accessors are again bypassed and SetValue() is used when the property needs to be modified. And when SetValue() is invoked, the propertyChanged delegate is executed after the property has changed (to be complete here, propertyChanging is invoked before the property change).

    You might wonder why bother defining the property if the bindable property is only used by xaml, or used in the context of Binding. Well, I said the property accessors weren't invoked, but they are used in the context of Xaml and XamlC:

    • a [TypeConverter] attribute can be defined on the property, and will be used
    • with XamlC on, the property signature can be used to infer, at compile time, the Type of the BindableProperty.

    So it's a good habit to always declare property accessors for public BindableProperties. ALWAYS.

    Why is my binding failing ?

    As you're using CustomMap as bot View and ViewModel (I won't tell the Mvvm Police), doing this in your constructor should be enough:

    BindingContext = this; //no need to prefix it with base.
    

    As you're doing it already, your Binding should work once you've modified the BindableProperty declaration in the way I explained earlier.

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Monday, September 19, 2016 1:13 PM