Monday, 7 March 2016

Xamarin.Forms Behaviors: EventHandlerBehavior and InvokeMethodAction

Previously, I demonstrated using the EventHandlerBehavior and InvokeCommandAction classes to invoke one or more commands when an event fires. 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 EventHandlerBehavior and InvokeMethodAction classes to invoke one or more methods when an event fires.

Invoking a Method when an Event Fires

The EventHandlerBehavior class listens for a specified event to occur, and executes one or more actions in response. It requires you to set an EventName property to the event that you want the behavior to listen to, and an Actions property to one or more actions that should be executed in response to the event firing.

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.

Calling a Method without Parameters

The following code shows an example of using the EventHandlerBehavior and InvokeMethodAction classes to invoke a method with no parameters:

<StackLayout.BindingContext> <local:InvokeMethodDemoPageViewModel /> </StackLayout.BindingContext> ... <Button Text="Invoke ViewModel Method"> <Button.Behaviors> <behaviors:EventHandlerBehavior EventName="Clicked"> <behaviors:InvokeMethodAction TargetObject="{Binding}" MethodName="IncrementCounter" /> </behaviors:EventHandlerBehavior> </Button.Behaviors> </Button>

When the Button.Clicked event fires, the IncrementCounter 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 InvokeMethodDemoPageViewModel instance. Note that the Actions property of the EventHandlerBehavior is set indirectly by creating the InvokeMethodAction instance as a child of the EventHandlerBehavior instance.

Calling Methods with Two Parameters

The InvokeMethodAction class also supports calling methods with parameters. However, methods with parameters must conform to a signature with two parameters such as (object sender, object params).

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

<StackLayout BindingContext="{x:Reference page}"> ... <Button Text="Invoke Page Method"> <Button.Behaviors> <behaviors:EventHandlerBehavior EventName="Clicked"> <behaviors:InvokeMethodAction TargetObject="{Binding}" MethodName="OnButtonClicked" /> </behaviors:EventHandlerBehavior> </Button.Behaviors> </Button> </StackLayout>

When the Button.Clicked event fires, the OnButtonClicked method is executed (which has a signature of object sender, EventArgs args). 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 EventHandlerBehavior is set indirectly by creating the InvokeMethodAction instance as a child of the EventHandlerBehavior instance.

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

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

Summary

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

2 comments:

  1. I, good code!
    A question: I cannot make it work with PropertyChanged, the behavior seems to be ignored, the "AddedEvent" method is not executed.
    The method is in Page binding, while Label has another binding, is this the problem? How can I resolve?
    I have used with ItemTapped in ListView without problems.

    My code:
    <Label Text="{Binding LoadedEvent.SubTitle}" VerticalOptions="Center" HorizontalOptions="Center"
    x:Name="nextEvent_subTitle" Style="{DynamicResource eventSubTitle}">
    <Label.Behaviors>
    <behaviors:EventHandlerBehavior EventName="PropertyChanged">
    <behaviors:InvokeMethodAction TargetObject="{Binding}" MethodName="AddedEvent" />
    </behaviors:EventHandlerBehavior>
    </Label.Behaviors>
    &lt/Label>
    b.r.
    Cristiano Larghi

    ReplyDelete
  2. Apologies for the much delayed response.

    Using InvokeMethodAction can be problematic, because it's not always easy to use (which is a limitation of Microsoft's original 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 assume it's on the code-behind for the XAML file. Therefore, the InvokeMethodAction.TargetObject property should be set to the page (and so your ContentPage, or whichever page type you're using, must have x:Name="page"):

    <Label Text="{Binding Message}">
    <Label.Behaviors>
    <behaviors:EventHandlerBehavior EventName="PropertyChanged">
    <behaviors:InvokeMethodAction TargetObject="{Binding Source={x:Reference page}}" MethodName="AddedEvent" />
    </behaviors:EventHandlerBehavior>
    </Label.Behaviors>
    </Label>

    The second issue is the method declaration itself. It must take zero arguments (e.g. public void AddedEvent), or it must take two arguments. If it takes two arguments, the first must be of type 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 PropertyChangedEventArgs. Therefore, your event handler must have the signature:

    public void AddedEvent(object sender, System.ComponentModel.PropertyChangedEventArgs args)
    {

    }

    Hope this makes sense.

    ReplyDelete