Xamarin.Forms Bindable Properties
Bindable properties extend CLR property functionality by backing a property with a BindableProperty
type, instead of backing a property with a field. The purpose of bindable properties is to provide a property system that supports data binding, styles, templates, and values set through parent-child relationships. In addition, bindable properties can provide default values, validation of property values, and callbacks that monitor property changes.
Properties should be implemented as bindable properties to support one or more of the following features:
- Acting as a valid target property for data binding.
- Setting the property through a style.
- Providing a default property value that's different from the default for the type of the property.
- Validating the value of the property.
- Monitoring property changes.
Examples of Xamarin.Forms bindable properties include Label.Text
, Button.BorderRadius
, and StackLayout.Orientation
. Each bindable property has a corresponding public static readonly
field of type BindableProperty
that is exposed on the same class and that is the identifier of the bindable property. For example, the corresponding bindable property identifier for the Label.Text
property is Label.TextProperty
.
Create a bindable property
The process for creating a bindable property is as follows:
- Create a
BindableProperty
instance with one of theBindableProperty.Create
method overloads. - Define property accessors for the
BindableProperty
instance.
All BindableProperty
instances must be created on the UI thread. This means that only code that runs on the UI thread can get or set the value of a bindable property. However, BindableProperty
instances can be accessed from other threads by marshaling to the UI thread with the Device.BeginInvokeOnMainThread
method.
Create a property
To create a BindableProperty
instance, the containing class must derive from the BindableObject
class. However, the BindableObject
class is high in the class hierarchy, so the majority of classes used for user interface functionality support bindable properties.
A bindable property can be created by declaring a public static readonly
property of type BindableProperty
. The bindable property should be set to the returned value of one of the BindableProperty.Create
method overloads. The declaration should be within the body of BindableObject
derived class, but outside of any member definitions.
At a minimum, an identifier must be specified when creating a BindableProperty
, along with the following parameters:
- The name of the
BindableProperty
. - The type of the property.
- The type of the owning object.
- The default value for the property. This ensures that the property always returns a particular default value when it is unset, and it can be different from the default value for the type of the property. The default value will be restored when the
ClearValue
method is called on the bindable property.
Important
The naming convention for bindable properties is that the bindable property identifier must match the property name specified in the Create
method, with "Property" appended to it.
The following code shows an example of a bindable property, with an identifier and values for the four required parameters:
public static readonly BindableProperty EventNameProperty =
BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null);
This creates a BindableProperty
instance named EventNameProperty
, of type string
. The property is owned by the EventToCommandBehavior
class, and has a default value of null
.
Optionally, when creating a BindableProperty
instance, the following parameters can be specified:
- The binding mode. This is used to specify the direction in which property value changes will propagate. In the default binding mode, changes will propagate from the source to the target.
- A validation delegate that will be invoked when the property value is set. For more information, see Validation callbacks.
- A property changed delegate that will be invoked when the property value has changed. For more information, see Detect property changes.
- A property changing delegate that will be invoked when the property value will change. This delegate has the same signature as the property changed delegate.
- A coerce value delegate that will be invoked when the property value has changed. For more information, see Coerce value callbacks.
- A
Func
that's used to initialize a default property value. For more information, see Create a default value with a Func.
Create accessors
Property accessors are required to use property syntax to access a bindable property. The Get
accessor should return the value that's contained in the corresponding bindable property. This can be achieved by calling the GetValue
method, passing in the bindable property identifier on which to get the value, and then casting the result to the required type. The Set
accessor should set the value of the corresponding bindable property. This can be achieved by calling the SetValue
method, passing in the bindable property identifier on which to set the value, and the value to set.
The following code example shows accessors for the EventName
bindable property:
public string EventName
{
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}
Consume a bindable property
Once a bindable property has been created, it can be consumed from XAML or code. In XAML, this is achieved by declaring a namespace with a prefix, with the namespace declaration indicating the CLR namespace name, and optionally, an assembly name. For more information, see XAML Namespaces.
The following code example demonstrates a XAML namespace for a custom type that contains a bindable property, which is defined within the same assembly as the application code that's referencing the custom type:
<ContentPage ... xmlns:local="clr-namespace:EventToCommandBehavior" ...>
...
</ContentPage>
The namespace declaration is used when setting the EventName
bindable property, as demonstrated in the following XAML code example:
<ListView ...>
<ListView.Behaviors>
<local:EventToCommandBehavior EventName="ItemSelected" ... />
</ListView.Behaviors>
</ListView>
The equivalent C# code is shown in the following code example:
var listView = new ListView ();
listView.Behaviors.Add (new EventToCommandBehavior
{
EventName = "ItemSelected",
...
});
Advanced scenarios
When creating a BindableProperty
instance, there are a number of optional parameters that can be set to enable advanced bindable property scenarios. This section explores these scenarios.
Detect property changes
A static
property-changed callback method can be registered with a bindable property by specifying the propertyChanged
parameter for the BindableProperty.Create
method. The specified callback method will be invoked when the value of the bindable property changes.
The following code example shows how the EventName
bindable property registers the OnEventNameChanged
method as a property-changed callback method:
public static readonly BindableProperty EventNameProperty =
BindableProperty.Create (
"EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
...
static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
}
In the property-changed callback method, the BindableObject
parameter is used to denote which instance of the owning class has reported a change, and the values of the two object
parameters represent the old and new values of the bindable property.
Validation callbacks
A static
validation callback method can be registered with a bindable property by specifying the validateValue
parameter for the BindableProperty.Create
method. The specified callback method will be invoked when the value of the bindable property is set.
The following code example shows how the Angle
bindable property registers the IsValidValue
method as a validation callback method:
public static readonly BindableProperty AngleProperty =
BindableProperty.Create ("Angle", typeof(double), typeof(HomePage), 0.0, validateValue: IsValidValue);
...
static bool IsValidValue (BindableObject view, object value)
{
double result;
bool isDouble = double.TryParse (value.ToString (), out result);
return (result >= 0 && result <= 360);
}
Validation callbacks are provided with a value, and should return true
if the value is valid for the property, otherwise false
. An exception will be raised if a validation callback returns false
, which should be handled by the developer. A typical use of a validation callback method is constraining the values of integers or doubles when the bindable property is set. For example, the IsValidValue
method checks that the property value is a double
within the range 0 to 360.
Coerce value callbacks
A static
coerce value callback method can be registered with a bindable property by specifying the coerceValue
parameter for the BindableProperty.Create
method. The specified callback method will be invoked when the value of the bindable property changes.
Important
The BindableObject
type has a CoerceValue
method that can be called to force a reevaluation of the value of its BindableProperty
argument, by invoking its coerce value callback.
Coerce value callbacks are used to force a reevaluation of a bindable property when the value of the property changes. For example, a coerce value callback can be used to ensure that the value of one bindable property is not greater than the value of another bindable property.
The following code example shows how the Angle
bindable property registers the CoerceAngle
method as a coerce value callback method:
public static readonly BindableProperty AngleProperty = BindableProperty.Create (
"Angle", typeof(double), typeof(HomePage), 0.0, coerceValue: CoerceAngle);
public static readonly BindableProperty MaximumAngleProperty = BindableProperty.Create (
"MaximumAngle", typeof(double), typeof(HomePage), 360.0, propertyChanged: ForceCoerceValue);
...
static object CoerceAngle (BindableObject bindable, object value)
{
var homePage = bindable as HomePage;
double input = (double)value;
if (input > homePage.MaximumAngle)
{
input = homePage.MaximumAngle;
}
return input;
}
static void ForceCoerceValue(BindableObject bindable, object oldValue, object newValue)
{
bindable.CoerceValue(AngleProperty);
}
The CoerceAngle
method checks the value of the MaximumAngle
property, and if the Angle
property value is greater than it, it coerces the value to the MaximumAngle
property value. In addition, when the MaximumAngle
property changes the coerce value callback is invoked on the Angle
property by calling the CoerceValue
method.
Create a default value with a Func
A Func
can be used to initialize the default value of a bindable property, as demonstrated in the following code example:
public static readonly BindableProperty SizeProperty =
BindableProperty.Create ("Size", typeof(double), typeof(HomePage), 0.0,
defaultValueCreator: bindable => Device.GetNamedSize (NamedSize.Large, (Label)bindable));
The defaultValueCreator
parameter is set to a Func
that invokes the Device.GetNamedSize
method to return a double
that represents the named size for the font that is used on a Label
on the native platform.