Monday, 4 April 2016

Xamarin.Forms Behaviors: DataChangedBehavior and InvokeMethodAction

Previously, I demonstrated using the DataChangedBehavior and InvokeCommandAction classes to invoke one or more commands when data changes. The Behaviors Library for Xamarin.Forms has the notion of behaviors and actions. A behavior is attached to a control and listens for something to happen, such as an event firing. When the “something” happens, it triggers one or more actions, such as invoking a method or command. Actions are invoked by behaviors and executed on a selected control.

In this blog post, I’ll demonstrate using the DataChangedBehavior and InvokeMethodAction classes to invoke one or more methods when data changes.

Invoking a Method when Data Changes

The DataChangedBehavior class listens for the bound data to meet a specified condition, and executes one or more actions in response. It requires you to set the following properties:

  • Binding – an object that represents the bound object that the behavior will listen to.
  • ComparisonCondition – a ComparisonCondition enumeration value that represents the comparison to be performed between the values of the Binding and Value properties. The enumeration values are: Equal, NotEqual, LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual.
  • Value – an object that represents the value to be compared with the value of the Binding property.
  • Actions – one or more actions that should be executed in response to the bound data changing. Note that this property is set indirectly.

The InvokeMethodAction class executes a specified method when invoked. It requires you to set a TargetObject property to an object that exposes the method of interest, and a MethodName property to the name of the method to be invoked. The InvokeMethodAction class allows you to call a method with zero parameters, or a method with two parameters. For more information about calling a method with parameters, see EventHandlerBehavior and InvokeMethodAction.

Calling a Method without Parameters

The following code shows an example of using the DataChangedBehavior and InvokeMethodAction classes to invoke a method without parameters:

<StackLayout BindingContext="{x:Reference page}"> <Picker x:Name="picker" Title="Colours"> <Picker.Items> <x:String>Red</x:String> <x:String>Blue</x:String> <x:String>Green</x:String> </Picker.Items> <Picker.Behaviors> <behaviors:DataChangedBehavior Binding="{Binding Path=SelectedIndex, Source={x:Reference picker}}" ComparisonCondition="NotEqual" Value="-1"> <behaviors:InvokeMethodAction TargetObject="{Binding}" MethodName="OnPickerChanged" /> </behaviors:DataChangedBehavior> </Picker.Behaviors> </Picker> <StackLayout Orientation="Horizontal"> <Label Text="Selected Item:" /> <Label x:Name="selectedItemLabel" /> </StackLayout> </StackLayout>

When the value of the Picker.SelectedIndex property becomes not equal to –1, the OnPickerChanged method is executed. The TargetObject property value specifies that the BindingContext exposes the method of interest. Therefore, the InvokeMethodAction class will search the BindingContext of the attached control for the method, which in this case is the ContentPage. Note that the Actions property of the DataChangedBehavior is set indirectly by creating the InvokeMethodAction instance as a child of the DataChangedBehavior instance.

Generally, combining the DataChangedBehavior and InvokeMethodAction classes offers no advantage over combining the DataChangedBehavior and InvokeCommandAction classes. However, the InvokeMethodAction class can be useful when combined with other behaviors.

The sample application that this code comes from can be downloaded from GitHub.

Summary

The DataChangedBehavior class listens for the bound data to meet a specified condition, and executes one or more actions in response. The InvokeMethodAction class executes a specified method when invoked. Generally, combining the DataChangedBehavior and InvokeCommandAction classes is preferred over combining the DataChangedBehavior and InvokeMethodAction classes. However, the InvokeMethodAction class can be useful when combined with other behaviors.

2 comments:

  1. Hello,
    This looks to be exactly what I've been looking for however I can't seem to get it to work.

    I have a listview bound to a collection and the items implement INotifyPropertyChanged. This seems to work fine. If a bound property changes in the object, the controls property is updated correctly.

    I have another property IsDone that isn't bound to a control and this is the property I need an event triggered for when it changes.
    I tried adding a Label and binding the text property to the IsDone property and setting the IsVisible = False for the label so it is not seen.
    I tried using your DataChangedBehavior but nothing seems to happen. I don't get an error and my method never seems to get called.

    Label x:Name="labelIsDone" IsVisible="True" Text="{Binding IsDone}"
    Label.Behaviors
    local2:DataChangedBehavior Binding="{Binding Path=Text, Source={x:Reference labelIsDone}}" ComparisonCondition="Equal" Value="True"
    local2:InvokeMethodAction TargetObject="{Binding}" MethodName="OnIsDoneChanged"
    local2:DataChangedBehavior
    Label.Behaviors
    Label

    I had to remove the greater and less then since they weren't allowed.

    Any idea what I'm doing wrong?

    Thanks,
    Joe

    ReplyDelete
  2. Sure. Using InvokeMethodAction can be problematic, because it's not always easy to use (which is a limitation of Microsoft's CallMethodAction, which InvokeMethodAction is based on).

    There's two issues to making your code work. The first is ensuring that the InvokeMethodAction knows where to look for the event handler. I assumed it was in the code behind for the XAML file. Therefore, the InvokeMethodAction.TargetObject property is set to the page (and so your ContentPage, or whichever page type you're using, must have x:Name="page"):

    <Label x:Name="doneLabel" Text="{Binding IsDone}">
    <Label.Behaviors>
    <behaviors:DataChangedBehavior Binding="{Binding Source={x:Reference doneLabel}, Path=Text}" ComparisonCondition="Equal" Value="True">
    <behaviors:InvokeMethodAction TargetObject="{Binding Source={x:Reference page}" MethodName="OnIsDoneChanged" />
    </behaviors:DataChangedBehavior>
    </Label.Behaviors>
    </Label>

    The second issue is the method declaration itself. It must take zero arguments (e.g. public void OnIsDoneChanged() ), or it must take two arguments. If it takes two arguments, the first must be an object, and the second must be of the type of the argument being passed to it. In the case of your example the data being passed to it is the value of the Label.Text property. Therefore, your event handler must have the signature:

    public void OnIsDoneChanged(object sender, string value)
    {
    ...
    }

    Hope all this makes sense.

    ReplyDelete