0

I copied/wrote a class that inherits from Frame

public class Circle : Frame
{
    //private double _radius;

    public static readonly BindableProperty RadiusProperty = BindableProperty.Create(nameof(Radius), typeof(double), typeof(Circle), 126.0, BindingMode.TwoWay);
    public double Radius
    {
        get => (double)GetValue(RadiusProperty); //_radius;
        set
        {
            SetValue(RadiusProperty, value);
            OnPropertyChanged();
            AdjustSize();
        }
    }

    private void AdjustSize()
    {
        HeightRequest = Radius;
        WidthRequest = Radius;
        Margin = new Thickness(0,0,0,0);
        Padding = new Thickness(0, 0, 0, 0);
        CornerRadius = (float) (Radius / 2);
    }

    public Circle()
    {
        HorizontalOptions = LayoutOptions.Center;
    }
}

The consuming page defines these BinadableProperties

    public static readonly BindableProperty InnerColorProperty = BindableProperty.Create("InnerColor", typeof(Color), typeof(CircleProgressView), defaultValue: Color.FromHex("#34495E"), BindingMode.TwoWay);
    public Color InnerColor
    {
        get => (Color)GetValue(InnerColorProperty);
        set => SetValue(InnerColorProperty, value);
    }

    public static readonly BindableProperty InnerRadiusProperty = BindableProperty.Create("InnerRadius", typeof(double), typeof(CircleProgressView), 126.0, BindingMode.TwoWay);
    public double InnerRadius
    {
        get => (double)GetValue(InnerRadiusProperty);
        set => SetValue(InnerRadiusProperty, value);
    }

And uses the Circle like so

<components:Circle Grid.Row="0" BackgroundColor="{Binding InnerColor}" Radius="{Binding InnerRadius}" >

Alas, the bindable's setter, and hence AdjustSize(), is never called nor is the default value used. Instead of a circle I end up with a rectangle. The BackgroundColor, which is a property of Frame, binds and works fine.

If I remove the BindableProperty and leave behind a regular INotify property

public class Circle : Frame
{
    private double _radius;

    public double Radius
    {
        get => _radius;
        set
        {
            _radius = value;
            OnPropertyChanged();
            AdjustSize();
        }
    }

    private void AdjustSize()
    {
        HeightRequest = Radius;
        WidthRequest = Radius;
        Margin = new Thickness(0,0,0,0);
        Padding = new Thickness(0, 0, 0, 0);
        CornerRadius = (float) (Radius / 2);
    }

    public Circle()
    {
        HorizontalOptions = LayoutOptions.Center;
    }
}

The compiler complains if I keep the InnerRadius binding

Severity Code Description Project File Line Suppression State Error Position 17:92. No property, bindable property, or event found for 'Radius', or mismatching type between value and property. ...\Components\CircleProgressView.xaml 17

I can replace the Radius binding with a hardcoded value and it runs fine, a circle appears.

<components:Circle Grid.Row="0" BackgroundColor="{Binding InnerColor}" Radius="126" >

What's wrong with a BindableProperty in a regular C# class?

John Mc
  • 212
  • 2
  • 16
  • To handle property changes you want to use [property-changed callbacks](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/bindable-properties#detect-property-changes) – Sharada Gururaj Apr 05 '20 at 03:28
  • Also would recommend that you avoid using two-way binding mode (you are not expecting radius to be an input) – Sharada Gururaj Apr 05 '20 at 03:29

1 Answers1

0

Firstly, we need to handle data in the property changed event of bindable property instead of the setter method of a normal property. So modify your Circle class like:

public static readonly BindableProperty RadiusProperty = BindableProperty.Create(nameof(Radius), typeof(double), typeof(Circle), 125.0, BindingMode.TwoWay, propertyChanged: RadiusChanged);
public double Radius
{
    get => (double)GetValue(RadiusProperty); //_radius;
    set => SetValue(RadiusProperty, value);
}
static void RadiusChanged(BindableObject bindableObject, object oldValue, object newValue)
{
    Circle circle = bindableObject as Circle;
    circle.HeightRequest = (double)newValue;
    circle.WidthRequest = (double)newValue;
    circle.CornerRadius = (float)((double)newValue / 2);
}

This is because we bind data in XAML we should manipulate the bindable property's changed event directly.

Secondly, I saw you bound the property using the parent page's bindable property. Normally, we won't do that. We will consume a view model as the page's binding context and then bind the property to the binding context. However, if you do want to consume the parent page's bindable property as the Circle's binding context, try this way:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Sample.SecondPage"
             xmlns:components="clr-namespace:Sample"
             x:Name="Page">
    <ContentPage.Content>
        <StackLayout>
            <components:Circle BackgroundColor="{Binding InnerColor, Source={x:Reference Page}}" Radius="{Binding InnerRadius, Source={x:Reference Page}}"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Name your parent page first and change the circle's source to that.

Here, I used a different default Radius value comparing to InnerRadius so the property changed event will be called at the initial time.

Ax1le
  • 6,563
  • 2
  • 14
  • 61
  • Thank you, that did the trick. I will mark as Answer but I request one clarification. You said "Secondly, I saw you bound the property using the parent page's bindable property. Normally, we won't do that." How does that work for reusable XAML components? – John Mc Apr 06 '20 at 15:05
  • @JohnMc We could use a separate class to define a reusable custom control. But we usually use binding context to populate the data instead of the parent view's bindable property. – Ax1le Apr 07 '20 at 01:44
  • I do but it wont fit here. – John Mc Apr 10 '20 at 15:09
  • Hi @JohnMc If it solved your issues could you please mark it as an answer? – Ax1le Apr 22 '20 at 08:26