Actions as BindableProperty

You are currently creating your own ContentView in Xamarin.Forms and are wondering how to create properties that can then be bound?
Easy going, the keyword is: BindableProperty.

example

Let’s start with a simple example. I have a ContentView with a button that executes a task defined behind it (which task this is, doesn’t matter in this case).

public static readonly BindableProperty ExecutableTaskProperty = BindableProperty.Create(
  nameof(ExecutableTask),
  typeof(string),
  typeof(TaskStarter),
  string.Empty);

public string ExecutableTask
{
  get { return GetValue(ExecutableTaskProperty)?.ToString(); }
  set { SetValue(ExecutableTaskProperty, value); }
}

Well, what have we here? Our own property is called ExceutableTask. We will see this name from the outside when we use it later. Here we create a public static readonly property of the type BindableProperty. The name of our BindableProperty must be exactly the same as our actual property, PLUS the postfix “Property” (has anyone counted how often the word “Property” appears in this post? ;)) — that is, “ExecutableTaskProperty“. If not, Xamarin cannot resolve the relationship between the two and we wonder why it doesn’t work.

Create method and parameters

The Create method creates our bindable variable, but requires a number of parameters first.
Here are the mandatory parameters (there are a few optional ones):

The Create method creates our bindable variable, but requires a number of parameters first.
Here are the mandatory parameters (there are a few optional ones):

  • propertyName — The name that will later be published externally for use (with nameof you can avoid hardcoded strings)
  • returnType — The type of our property, in our case string
  • declaringType —¬† Attention! The type of our ContentView (my ContentView is called TaskStarter)

The following are optional parameters:

  • defaultValue — Which value is returned by default. string.Empty is useful to avoid null
  • defaultBindingMode — Default “OneWay”, but can also be set to any other (with the enum BindingMode)

Thirdly, the method also accepts some delegates to react to changes in the property:

  • validateValue — is used to validate the input
  • propertyChanged — is invoked after the property has been changed
  • propertyChanging — is invoked before the property is changed

GetValue and SetValue

If we look at our actual property, we will see that the methods GetValue() and SetValue() are called in the getter and setter. These are necessary to get the data from the BindableProperty and to transport it into it, if we work with it in our ContentView class. It therefore serves as a kind of “storage” for our value. I think the call of both methods is self-explanatory.

use the ContentView

The implementation of the bindable property is also trivial:

<scheduler:TaskStarter Grid.Row="2" 
  OnTaskFinished="{Binding OnMyTaskFinished}" 
  ExecutableTask="{Binding MyTask}"
  HorizontalOptions="FillAndExpand" />

Our ContentView offers us the property ExecutableTask and we can use it. Here you can also see a second property, which I have created: OnTaskFinished.

use Actions and delegates

Since we could use any type we desire, we can of course also work with delegates here. To pick up the implementation from above, let’s take a look at the property “OnTaskFinished“:

public static readonly BindableProperty OnTaskFinishedProperty = BindableProperty.Create(nameof(OnTaskFinished),
      typeof(Action<bool, string>),
      typeof(TaskStarter),
      null);

public Action<bool, string> OnTaskFinished
{
  get 
  { 
    return (Action<bool, string>) GetValue(OnTaskFinishedProperty); 
  }
  set { SetValue(OnTaskFinishedProperty, value); }
}

The type Action<bool, string> was used here to return later whether the task was successful and a message in addition. The name here also contains the postfix “Property”, combined with the actual name.

In our code of the ContentView we can now easily invoke the delegate when we think the task was executed (don’t forget the question mark for null checking):

OnTaskFinished?.Invoke(success, msg);

troubleshooting

Your property is always null? I was faced with this problem too and had to search for a while. The solution was quite simple: I used a ViewModel in my ContentView and wanted to set this as a BindingContext. But my problem was that I took the BindingContext from the ContentView itself, so this. This is not possible(!). Instead, you have to take the BindingContext from the Content.

from

this.BindingContext = viewModel = new TaskStarterViewModel();

it will be

Content.BindingContext = viewModel = new TaskStarterViewModel();

And now everything else works as well.