Tuesday 8 December 2020

Xamarin Forms Triggers vs Behaviors vs Effects

 

Xamarin Forms Triggers vs Behaviors vs Effects


With Xamarin Forms latest 1.3 version, Xamarin added a big improvement to the platform: Support for Triggers and behaviors. Triggers allow us to conditionally make actions within XAML, whereas behaviors allow us to modify and increment the default behavior of any control.


> Modify the UI based on certain criteria.

> Data or events such as Triggers, Behaviors or Effects.


Triggers

Triggers take a trigger source such as an event and then can change a property on that control in response to the trigger.

 

A Trigger is an action fired after a certain situation. This situation is defined in XAML with the Trigger declaration. Each trigger could be composed of one or more TriggerActions.

 

A Trigger, let you add actions to the graphic controls from the XAML, allowing to change the appearance of your controls when a specific action happens. This actions can be: When an event happens or when a property control is changed.

 

A trigger is a condition (a property change or the firing of an event) that results in a response (another property change or running some code). The Triggers property of VisualElement and Style is of type IList<TriggersBase>

 

TriggerBase is an abstract class from which four sealed classes derive:

“Base class for classes that contain a condition and a list of actions to perform when the condition is met.”

 

public abstract class TriggerBase : Xamarin.Forms.BindableObject

  • Trigger for responses based on property changes.

  • EventTrigger for responses based on event firings.

  • DataTrigger for responses based on data bindings.

  • MultiTrigger for responses based on multiple triggers.

What are one of the main terms I need to know?

To declare a Trigger, you might know the following triggers parts:

? TargetType     Is the control type that will be getting the trigger. For example: Button.

? Property         Is the property that is observed by the Trigger to be triggered. For example: Is Focused.

? Value              Is the value that must have the Property mentioned above. This value next to the Property will be observed by the Trigger to be triggered. For example: Is   Focused = True.

? Setter       Sets the action that will get the control when the trigger is activated. Here the Property and Value must be established too. For example: Add a blue Background color. ( I show some examples bellow).

Style Triggers

You can define Triggers inside a Style for a View. These Triggers are binded to a property of the View and a value for that property. If the property gets the exact value, the Trigger is launched, executing the Setters placed inside it, as you can see in the next XAML snippet:

<Entry Placeholder="Enter your name" TextColor="Maroon" Opacity="0.5">

   <Entry.Style>

     <Style TargetType="Entry">

      <Style.Triggers>

        <Trigger Property="Entry.IsFocused" Value="True" TargetType="Entry">

          <Setter Property="Entry.Opacity" Value="1.0" />

          <Setter Property="Entry.TextColor" Value="Red" />

        </Trigger>

       </Style.Triggers>

     </Style>

   </Entry.Style>

</Entry>

 

In the code above, you can see a text entry (Entry) defined with an inline style. Inside the style the is a Triggers section with one Trigger defined. This Trigger is looking for the IsFocused property. When it value is true, the control is focused, the setters inside the Trigger are executed. In this example, the opacity of the View is stablished to 1.0 (totally opaque) and the text color is changed to Red:

This way, you can add interactivity in your styles in an easy way.

 View Triggers

View Triggers are slighty different than Style Triggers. In first place, a View Trigger can be driven by a raising event, a data comparision or a multiple data comparision. For example the following code defines an EventTrigger, handling the Clicked event of a button:

<Button Text="Event Trigger button">

  <Button.Triggers>

  <EventTrigger Event="Clicked">

</EventTrigger>

</Button.Triggers>

</button>

When the button is clicked, the Action you put inside the EventTrigger is invoked. But, what action? Well, you can create your own actions indeed. The only you need is to create a new public class implementing the TriggerAction <T>base class:

public class ButtonTrigger : TriggerAction<Button>

{

    protected override async void Invoke(Button sender)

    {

      await sender.FadeTo(0, 2000, Easing.CubicIn);

      await sender.FadeTo(1, 2000, Easing.CubicOut);

    }

}

 

In the TriggerAction class there is a method called Invoke you need to override in your derived class. This method is the one actually invoked when the trigger is launched. In the code above, the class ButtonTrigger overrides the Invoke method and do two FadeTo animations, one FadeOut and one FadeIn.

Now you only need to add the namespace where the ButtonTrigger class is located to your XAML and you are going to be able to add this action to your Trigger:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:triggers="clr-namespace:BehaviorsTriggers.Triggers;

                             assembly=BehaviorsTriggers"

 

             x:Class="BehaviorsTriggers.Views.MainView">

 

And there it is! Now you can add the ButtonTrigger action to your Event Trigger:

<Button Text="Event Trigger button">

  <Button.Triggers>

    <EventTrigger Event="Clicked">

     <triggers:ButtonTrigger/>

    </EventTrigger>

  </Button.Triggers>

</Button>

 

This is a very powerful tool to create little reusable pieces of code and add interactivity to your XAML without the need to use C#.

The basic principle is, a trigger is activated and you set properties or invoke code. Below are the following triggers you can use.

  • Property

  • Data

  • Event

  • Multi Trigger

When using Xamarin Forms sometimes we have a control in our XAML that changes according to the value of a property in our model. 


For example, if we have a Label which property Text has a binding to a string property that represents a Name in a model that we want to be visible only if isn’t empty.A quick way to solve it is to add a new bool property in the model to validate if the name has a value or not. Then set this property as the Binding to property IsVisible of the Label.



But why to add this UI logic to our model?  It should be handled in the XAML instead.


There are better ways to do it, one is by using Triggers, which basically allows you to change property values of a control based on events or property changes.

In Xamarin Forms there are 4 types of triggers:

  • Property Triggers

  • Data trigger (more commonly used in my case)

  • Event Triggers

  • Multi triggers

Property Triggers

The property triggers are used when you want to change a property of the control according to a specific value of another property.

The trigger from an Entry control is activated when is Focused. The action triggered just changes the Entry Text and the BackGroundColor properties.

 <Entry Placeholder="Write a text">

            <Entry.Triggers>

               <Trigger TargetType="Entry"

                        Property  ="IsFocused"

                        Value     ="True">

                     <Setter Property="Text"             Value="Trigger activated" />

                     <Setter Property="BackgroundColor"  Value="#CDF781" />

               </Trigger>

            </Entry.Triggers>

    </Entry>

 

Property Change - Based on Another Property Value Change. 

For example, if we want to change the background color and scale of the entry when the text matches a specific string.

All the controls have a property called Triggers in which you can specify the property to evaluate with a specific value and the properties that will be changed when the trigger activate once the value match occurs.




Data Triggers

Are similar to the property triggers except that instead of a property we specify a binding property. This allow us to set binding between control properties or ViewModel/Model properties.

The behavior is similar to the Property Triggers with the difference that Data Triggers checks the property from a specific control to apply the Setter established in other control. To check it, this trigger type, uses the Binding property. To get the Binding property you have to use the x:Reference followed on the control name. (In this case, Entry name).

 <!-- Data Trigger -->

        <Entry Placeholder="Write a text"

               x:Name     ="TextEntry" />

      

        <Button Text            ="Click here"

                BackgroundColor ="#C39BD3">

            <Button.Triggers>

               <DataTrigger TargetType="Button"

                            Binding   ="{Binding Source={x:Reference TextEntry}, Path=Text.Length}" 

                            Value     ="0">

                     <Setter Property="Text"             Value="Trigger activated" />

                     <Setter Property="BackgroundColor"  Value="#5DADE2" />                

               </DataTrigger>

            </Button.Triggers>

        </Button>

 


Binding between controls :


Allows you to do binding between controls properties. For example, in the image above the second Entry is enabled only if the first Entry has text.



Binding with a property in the ViewModel/Model

You can set binding to any property, so if we go back to the first example about hiding the Label if text is empty. If we do it with a data trigger would be something like this:


Event Triggers

It activates when an event of the control occurs. For example, if we want to change a property when a button is clicked (Using the event Clicked) or when the text change (Using the event TextChanged).

In this example we will add a scale animation when a button is tapped.


You just have to:

1-Create a class that extends from TriggerAction < Your Control type > and override Invoke method, inside it modify the properties of the control you want when the trigger activates.


2-Add the trigger to your control, specifying the event that will raise it on the Event property and add your custom trigger action class inside it.


Multi Triggers

Same as DataTrigger but it allows you to validate combining multiple conditions.

For example, if we want to enable a button according to the entries matching with a specific username and password.


The code is really simple you just have to add the MultiTrigger to your Button.



MultiTrigger is not needed frequently but there are some situations where it is very handy. MultiTrigger behaves similarly to Trigger or DataTrigger but it has multiple conditions. All the conditions must be true for a Setters to fire. Here is a simple example:

<!-- Text field needs to be initialized in order for the trigger to work at start -->

<Entry x:Name="email" Placeholder="Email" Text="" />

<Entry x:Name="phone" Placeholder="Phone" Text="" />

<Button Text="Submit">

    <Button.Triggers>

        <MultiTrigger TargetType="Button">

            <MultiTrigger.Conditions>

                <BindingCondition Binding="{Binding Source={x:Reference email}, Path=Text.Length}" Value="0" />

                <BindingCondition Binding="{Binding Source={x:Reference phone}, Path=Text.Length}" Value="0" />

            </MultiTrigger.Conditions>

            <Setter Property="IsEnabled" Value="False" />

        </MultiTrigger>

    </Button.Triggers>

</Button>


The example has two different entries, phone and email, and one of them is required to be filled. The MultiTrigger disables the submit button when both fields are empty.

Triggers are an easy way to add some UX responsiveness to your application. One easy way to do this is to add a Trigger which changes a Label's TextColor based on whether its related Entry has text entered into it or not.


Using a Trigger for this allows the Label.TextColor to change from gray (when no text is entered) to black (as soon as the users enters text):


Converter (each converter is given an Instance variable which is used in the binding so that a new instance of the class is not created each time it is used):

// <summary>

/// Used in a XAML trigger to return <c>true</c> or <c>false</c> based on the length of <c>value</c>.

/// </summary>

public class LengthTriggerConverter : Xamarin.Forms.IValueConverter {


    /// <summary>

    /// Used so that a new instance is not created every time this converter is used in the XAML code.

    /// </summary>

    public static LengthTriggerConverter Instance = new LengthTriggerConverter();


    /// <summary>

    /// If a `ConverterParameter` is passed in, a check to see if <c>value</c> is greater than <c>parameter</c> is made. Otherwise, a check to see if <c>value</c> is over 0 is made.

    /// </summary>

    /// <param name="value">The length of the text from an Entry/Label/etc.</param>

    /// <param name="targetType">The Type of object/control that the text/value is coming from.</param>

    /// <param name="parameter">Optional, specify what length to test against (example: for 3 Letter Name, we would choose 2, since the 3 Letter Name Entry needs to be over 2 characters), if not specified, defaults to 0.</param>

    /// <param name="culture">The current culture set in the device.</param>

    /// <returns><c>object</c>, which is a <c>bool</c> (<c>true</c> if <c>value</c> is greater than 0 (or is greater than the parameter), <c>false</c> if not).</returns>

    public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)     { return DoWork(value, parameter); }

    public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) { return DoWork(value, parameter); }


    private static object DoWork(object value, object parameter) {

        int parameterInt = 0;


        if(parameter != null) { //If param was specified, convert and use it, otherwise, 0 is used


            string parameterString = (string)parameter;


            if(!string.IsNullOrEmpty(parameterString)) { int.TryParse(parameterString, out parameterInt); }

        }


        return (int)value > parameterInt;

    }

}

XAML (the XAML code uses the x:Name of the Entry to figure out in the Entry.Text property is over 3 characters long.):

<StackLayout>

    <Label Text="3 Letter Name">

        <Label.Triggers>

            <DataTrigger TargetType="Label"

                         Binding="{Binding Source={x:Reference NameEntry},

                                           Path=Text.Length,

                                           Converter={x:Static helpers:LengthTriggerConverter.Instance},

                                           ConverterParameter=2}"

                         Value="False">

                <Setter Property="TextColor"

                        Value="Gray"/>

            </DataTrigger>

        </Label.Triggers>

    </Label>

    <Entry x:Name="NameEntry"

           Text="{Binding MealAmount}"

           HorizontalOptions="StartAndExpand"/>

</StackLayout>



Behaviors Vs Triggers 

Triggers : A Trigger is an action fired after a certain situation. This situation is defined in XAML with the Trigger declaration. Each trigger could be composed of one or more TriggerActions

 

Behaviors : Behaviors are meant to extend the View you apply them to far beyond the normal use.

Behaviors lets you add functionality to user interface controls without having to subclass them. Behaviors are written in code and added to controls in XAML or code.

Behaviors

Behaviors let you attach a piece of functionality to an element in a view. This feature can be reusable and give us an easy way to do unit testing.

After seeing the Triggers, now is time to take a look into the Behaviors. At first sight it could seem like Trigger and behaviors are the same thing. But not, not they are. While Triggers are driven by conditions you set in XAML and their final use is to execute code under some circumstances, Behaviors are meant to extend the View you apply them to far beyond the normal use.

Behaviors allow you to enhance the functionality of a Xamarin Forms control without sub classing. Using the Behaviors property you can add behaviors that attached to the Control and can execute code when events of that control are raised.


Behaviors, which are extremely useful for attaching a piece of functionality to an existing element in a view. Technically, they are C# classes that inherit from Behavior<T> where T is the UI element to which a functionality is attached. Behaviors are powerful because they are reusable and easy to incorporate into unit testing since they are an independent piece of functionality. They can be used from the simplest example, like adding an email text validator to the Entry element, to advanced situations like creating a rating control using tap gesture recognizer in a view.


A behavior class must inherit from Behavior<T> and override two methods OnAttachedTo(T view) and OnDetachingFrom(T view).

{

    protected override void OnAttachedTo (Entry bindable)

    {

...

    }

    protected override void OnDetachingFrom (Entry bindable)

    {

...

    }

}

  • OnAttachedTo – This method is fired immediately after the behavior is attached to the view. You can use this method to add a specific functionality to the UI element. For example, you can subscribe to the TextChanged event of the Entry control and validate the text for email.

The OnAttachedTo method is fired immediately after the behavior is attached to a control. This can be used to register event handlers or perform other setup that's required to support the behavior functionality.



  • OnDetachingFrom – This method is fired when the behavior is removed from the attached control. You can use this method to remove the TextChanged event handlers that were previously attached to avoid memory leaks.

 

The OnDetachingFrom method is fired when the behavior is removed from the control. This method receives a reference to the control to which it is attached, and is used to perform any required cleanup.

 

XAML

Every VisualElement has a behavior collection. Behaviors are attached to the element by simply adding the behavior to that collection.

<Entry Placeholder="Sample">

  <Entry.Behaviors>

    <local:SampleBehavior  />

  </Entry.Behaviors>

</Entry>


Email Validator Behavior

Validating emails from user keyed text is the most common requirement in an app these days, and this is something that you might need to use more than once. Wouldn’t it be nice to simply add the email validation logic to behaviors and attach them to the Entry element? Not just once, but as many Entry elements as you want?

To achieve this, all you need to do is create a behavior in the OnAttachedTo method by subscribing to the TextChanged event and adding your email validation logic within that. Be sure to unsubscribe the TextChanged handler in the OnDetachingFrom method.

public class EmailValidatorBehavior : Behavior

{

    const string emailRegex = @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +

        @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";


    static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(NumberValidatorBehavior), false);


    public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;


    public bool IsValid

    {

        get { return (bool)base.GetValue(IsValidProperty); }

        private set { base.SetValue(IsValidPropertyKey, value); }

    }


    protected override void OnAttachedTo(Entry bindable)

    {

        bindable.TextChanged += HandleTextChanged;

    }


    void HandleTextChanged(object sender, TextChangedEventArgs e)

    {

        IsValid = (Regex.IsMatch(e.NewTextValue, emailRegex, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));

        ((Entry)sender).TextColor = IsValid ? Color.Default : Color.Red;

    }


    protected override void OnDetachingFrom(Entry bindable)

    {

        bindable.TextChanged -= HandleTextChanged;


    }

}



PasswordValidationBehavior


using System.Text.RegularExpressions;  

using Xamarin.Forms;  

  

namespace DevenvExeBehaviors  

{  

    public class PasswordValidationBehavior : Behavior<Entry>  

    {  

        const string passwordRegex = @"^(?=.*[A-Za-z])(?=.*d)(?=.*[$@$!%*#?&])[A-Za-zd$@$!%*#?&]{8,}$";  

  

  

        protected override void OnAttachedTo(Entry bindable)  

        {  

            bindable.TextChanged += HandleTextChanged;  

            base.OnAttachedTo(bindable);  

        }  

  

        void HandleTextChanged(object sender, TextChangedEventArgs e)  

        {  

            bool IsValid = false;  

            IsValid = (Regex.IsMatch(e.NewTextValue, passwordRegex));  

            ((Entry)sender).TextColor = IsValid ? Color.Default : Color.Red;  

        }  

  

        protected override void OnDetachingFrom(Entry bindable)  

        {  

            bindable.TextChanged -= HandleTextChanged;  

            base.OnDetachingFrom(bindable);  

        }  

    }  

}  




IsValid – BindableProperty


There are a number of ways you can handle the validation results. For example, you can set the TextColor of the Entry field to red to indicate the invalid email, or you can create an IsValid BindableProperty and let the consuming code decide how the validation needs to be handled.

<Entry Grid.Row="0"

  Grid.Column="1"

        Placeholder="Email" >

<Entry.Behaviors>

  <local:EmailValidatorBehavior x:Name="emailValidator"/>

</Entry.Behaviors>

</Entry>

<Image Grid.Row="0"

  Grid.Column="2"

  x:Name="emailSuccessErrorImage"

  Style="{Binding Source={x:Reference emailValidator},

                        Path=IsValid,

                        Converter={StaticResource boolToStyleImage}}"/>


In the above code, BooleanToObject converter is used, which converts the IsValid boolean value to an appropriate image – a green tick is shown when true and a red error for false.


Behaviors are written for a specific control type (or a superclass that can apply to many controls), and they should only be added to a compatible control. Attempting to attach a behavior to an incompatible control will result in an exception being thrown.


Why Do We Need Behaviors?

Normally, we will write the validation code into the code at the backend because it directly interacts with the API of the control, so that we can create Behaviors and reuse them into the different controls. They can be used to provide a full range of the functionality to the controls, as shown below-

  • Number validation

  • Date validation

  • Email validation \

  • Password validation

  • Compare validation

Effects


When you have worked with Xamarin.Forms before you’ll know that the translation from the Forms control to a native control is done by renderers. This can be simplified with Effects. If you, as a developer, would want to change anything about the way Forms renders it for you, you would have to subclass the whole renderer and create your custom logic for it. This was considered a heavy-weight solution, especially when you would just want to change something basic like a color on a switch control.


Effects are an easy way to access these native features of controls. They are more or less similar to Custom Renderers, but a bit more lightweight. 


When you’re ready to extend the capability of Xamarin.Forms views beyond their out-of-the-box functionality, then it’s time to start customizing them using custom renderers, effects, and native views. Platform-specific controls and layouts have scores of features inaccessible using only the Xamarin.Forms abstraction. There are three ways to access those features and extend the functionality of your application. The custom renderer gives full control over a Xamarin.Forms control by allowing you to gain access to all of its native functionality. It allows you to override the methods of the default renderers provided by Xamarin.Forms or even replace the platform-specific control Xamarin.Forms used with another control. Xamarin.Forms developers’ customization approach of choice is effects, a milder form of customization. Use effects if you need to change properties or use event listeners. Finally, Xamarin.Forms supports the use of the native views directly in XAML. This provides full access to the power of native controls along with the full responsibility of managing their lifecycle and behavior.

Effects simplify the customization of control.

Developers can implement their own custom Renderer classes to customize the appearance and/or behavior of a control. However, implementing a custom renderer class to perform simple control customization is often a heavy-weight endeavor. Effects simplify this process, allowing the native controls on each platform to be more easily customized.

Effects allow the native controls on each platform to be customized, and are typically used for small styling changes. 

Why Effects instead of Custom Renderer?

  • An effect is when changing the properties of a platform-specific control will achieve the desired result.

  • A custom renderer is required when there's a need to override methods of a platform-specific control.

  • A custom renderer is required when there's a need to replace the platform-specific control that implements a Xamarin.Forms control.

 

The process for creating an effect in each platform-specific project is as follows:

  1. Create a subclass of the PlatformEffect class.

  2. Override the OnAttached method and write logic to customize the control.

  3. Override the OnDetached method and write logic to clean up the control customization, if required.

  4. Add a ResolutionGroupName attribute to the effect class. This attribute sets a company wide namespace for effects, preventing collisions with other effects with the same name. Note that this attribute can only be applied once per project.

  5. Add an ExportEffect attribute to the effect class. This attribute registers the effect with a unique ID that's used by Xamarin.Forms, along with the group name, to locate the effect prior to applying it to a control. The attribute takes two parameters – the type name of the effect, and a unique string that will be used to locate the effect prior to applying it to a control.

Creating the Effect on Each Platform

The following sections discuss the platform-specific implementation of the FocusEffect class.

IOS 

using Xamarin.Forms;

using Xamarin.Forms.Platform.iOS;

[assembly:ResolutionGroupName ("MyCompany")]

[assembly:ExportEffect (typeof(EffectsDemo.iOS.FocusEffect), nameof(EffectsDemo.iOS.FocusEffect))]

namespace EffectsDemo.iOS

{

    public class FocusEffect : PlatformEffect

    {

        UIColor backgroundColor;


        protected override void OnAttached ()

        {

            try {

                Control.BackgroundColor = backgroundColor = UIColor.FromRGB (204, 153, 255);

            } catch (Exception ex) {

                Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);

            }

        }


        protected override void OnDetached ()

        {

        }


        protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)

        {

            base.OnElementPropertyChanged (args);


            try {

                if (args.PropertyName == "IsFocused") {

                    if (Control.BackgroundColor == backgroundColor) {

                        Control.BackgroundColor = UIColor.White;

                    } else {

                        Control.BackgroundColor = backgroundColor;

                    }

                }

            } catch (Exception ex) {

                Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);

            }

        }

    }

}


Android


using System;

using Xamarin.Forms;

using Xamarin.Forms.Platform.Android;


[assembly: ResolutionGroupName("MyCompany")]

[assembly: ExportEffect(typeof(EffectsDemo.Droid.FocusEffect), nameof(EffectsDemo.Droid.FocusEffect))]

namespace EffectsDemo.Droid

{

    public class FocusEffect : PlatformEffect

    {

        Android.Graphics.Color originalBackgroundColor = new Android.Graphics.Color(0, 0, 0, 0);

        Android.Graphics.Color backgroundColor;


        protected override void OnAttached()

        {

            try

            {

                backgroundColor = Android.Graphics.Color.LightGreen;

                Control.SetBackgroundColor(backgroundColor);

            }

            catch (Exception ex)

            {

                Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);

            }

        }


        protected override void OnDetached()

        {

        }


        protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args)

        {

            base.OnElementPropertyChanged(args);

            try

            {

                if (args.PropertyName == "IsFocused")

                {

                    if (((Android.Graphics.Drawables.ColorDrawable)Control.Background).Color == backgroundColor)

                    {

                        Control.SetBackgroundColor(originalBackgroundColor);

                    }

                    else

                    {

                        Control.SetBackgroundColor(backgroundColor);

                    }

                }

            }

            catch (Exception ex)

            {

                Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);

            }

        }

    }

}


Consuming the Effect in XAML

<Entry Text="Effect attached to an Entry" ...>

    <Entry.Effects>

        <local:FocusEffect />

    </Entry.Effects>

    ...

</Entry>


Xamarin Forms framework has a lot of controls such as Label, Entry, Button, ListView, etc. Each one has their own appearance and behavior based on the platform you are running on. For example, this is how an Entry looks when running in iOS:



But what happens if you want to customize this appearance by adding an orange border to the Entry. The Xamarin Forms Entry doesn’t have a property to set the border color, so you will need a custom renderer or an effect to achieve this. Which will basically let you modify the appearance and behavior of the control.


Custom Renderer

So let’s do this using custom renderer in just six steps:

1-Create a control

The first thing we are going to do is creating a new class on our forms project. This class will extend from the control class we want to modify or extend.

2-Create a renderer class for each platform 

Now we will create a new class on each platform to support the functionality we want.

Is important to mention that I’m adding it in all the platform because I want to modify the entry on each one. In case you only want to modify it only in one then only add the renderer in that platform and the others will continue rendering the default control behavior.


3-Extend of the native control class


In this new class we will extend from the native renderer. In the following image, you will see which class to use according to the control you are modifying. For example, if you want to modify the BoxView then you will have to extend from the class BoxRenderer which basically will use the native UIView in iOS, ViewGroup in Android, etc


We are modifying the entry so we will extend from the EntryRenderer class.

4-Adding the custom code 

We are going to override the OnElementChanged method and add our customization code there. In case we want to handle property changes in runtime then is necessary to override the OnElementPropertyChanged method too and add the property handling code there as well.

Tip: If you don’t know much about native functionalities you can try googling how to do it in Xamarin iOS/Xamarin Android, or even in Java/Swift and then translate the code to C#. 

5-Add the ExportRenderer attribute 

In the first typeof we will indicate the Forms control it will use (Created in step one) and in the second one, we will add the type of the renderer class created.

In case we want to apply the renderer to all the entries, then in the first parameter instead of typeof(ExtendedEntry) we put typeof (Entry)

6-Use the control  

Add the reference of the control created in step 1 and use it.

Effects


Let’s do the same now but using an Effect


1-Create a new RoutingEffect class


In your forms project create a new BorderLineEffect class, and extend from RoutingEffect.

Add a group name and an effect name.

Tip: The group name is unique per project, so maybe is better to add it in a constant, so that you can use the same group name if you create more effects.



2-In each platform create a new effect class and extend from PlatformEffect

After doing that you will have to implement the following methods:


  • OnAttached: It occurs when the effect is attached, here you will add the customization code


  • OnDetached: It occurs when the effect is detached, here you will clean the customization done on the OnAttached method


Another optional method is the OnElementPropertyChanged, which will let you handle property changes at runtime.

3-Add the customization code inside the OnAttached/OnDetached method 

iOS:


Android:

4-Add attributes to your effect  

In the platform-specific projects, we need to add ResolutionGroupName and ExportEffect attributes

5-Use the effect

Add the reference of the effect created in step 1 and use it.

When to use effects and when to use custom renderers

As you see above we achieved the same using a custom renderer and effect.

The rules of when to use one or the other are:


Use effects when you want a small styling change 


A good rule could be to use it when you want to modify a property of the native control. For example changing the background style of a button, adding an underline to a label, adding a shadow, etc. That means that in the example we just did, the best option is to use an effect instead of a custom renderer.


Use custom renderer when you need to create a new control that doesn’t exist in Xamarin Forms


DrawView

To achieve the control in the image above, Is necessary to use a custom renderer because Xamarin Forms doesn’t have a DrawView, we will have to create our own inhering from View class.



public class DrawViewRenderer : ViewRenderer<DrawView, NativeDrawView>


Use custom renderer when you need to override native control method 


When using effects is not possible to override native methods, so for example in the sample above to detect when the user start painting you need to override the Touches method (TouchesEnded, TouchesBegin, TouchedEnd). To do that you need to use a custom renderer.

 


No comments:

Post a Comment

All About .NET MAUI

  What’s .NET MAUI? .NET MAUI (.NET Multi-platform App UI) is a framework for building modern, multi-platform, natively compiled iOS, Androi...

Ads2