Skip to content

Migrating Xamarin.Forms Effects

David Ortinau edited this page Apr 29, 2022 · 1 revision

Reusing Effects

Effects allow the native controls on each platform to be customized without having to resort to a custom renderer implementation.

With the new extensibility possibilities of .NET MAUI Handlers, the use of Effects will probably decrease. For example, we can customize the Entry control by eliminating the underline with just a few lines:

#if __ANDROID__
        Microsoft.Maui.Handlers.EntryHandler.EntryMapper.AppendToMapping("NoUnderline", (h, v) =>
        {
            h.NativeView.BackgroundTintList = ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
        });
#endif

However, effects still exist in .NET MAUI and porting effects from a Xamarin.Forms app is straightforward.

Effects in Xamarin.Forms

The process for creating an effect in Xamarin.Forms 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: 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.

The following example shows an Android effect named FocusEffect that changes the background color of control, based on whether it has focus:

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(PropertyChangedEventArgs args)
        {
            base.OnElementPropertyChanged(args);

            if (View == null)
                return;

            if(args.PropertyName == View.IsFocusedProperty.PropertyName)
            {
                try
                {
                    if(View.IsFocused)
                        Control.SetBackgroundColor(originalBackgroundColor);
                    else
                        Control.SetBackgroundColor(backgroundColor);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
                }
            }
        }

        // ...
    }
}

In this example, the OnAttached method calls the SetBackgroundColor method to set the background color of the control to LightGreen, and also stores this color in a field. This functionality is wrapped in a try/catch block in case the control the effect is attached to does not have a SetBackgroundColor property. No implementation is provided by the OnDetached method because no cleanup is necessary.

The OnElementPropertyChanged override responds to BindableProperty changes on the Xamarin.Forms control. When the IsFocused property changes, the background color of the control is changed to white if the control has focus, otherwise it's changed to light green. This functionality is wrapped in a try/catch block in case the control the effect is attached to does not have a BackgroundColor property.

The following XAML code example shows an Entry control to which the FocusEffect is attached:

<Entry Text="Effect attached to an Entry" ...>
    <Entry.Effects>
        <local:FocusEffect />
    </Entry.Effects>
    ...
</Entry>

The FocusEffect class subclasses the RoutingEffect class, which represents a platform-independent effect that wraps an inner effect that is usually platform-specific. The FocusEffect class calls the base class constructor, passing in a parameter consisting of a concatenation of the resolution group name (specified using the ResolutionGroupName attribute on the effect class), and the unique ID that was specified using the ExportEffect attribute on the effect class. Therefore, when the Entry is initialized at runtime, a new instance of the Effects.FocusEffect is added to the control's Effects collection.

public class FocusEffect : RoutingEffect
{
    public FocusEffect () : base ($"Effects.{nameof(FocusEffect)}")
    {
    }
}

Effects in .NET MAUI

A Xamarin.Forms effect can be migrated to .NET MAUI by simply changing the namespace from Xamarin.Forms to Microsoft.Maui.Controls:

using System;
using System.ComponentModel;
using Microsoft.Maui.Controls;

namespace Effects.Effects
{
    public class FocusRoutingEffect : RoutingEffect
    {

    }

#if WINDOWS
    public class FocusPlatformEffect : Microsoft.Maui.Controls.Compatibility.Platform.UWP.PlatformEffect
    {
        public FocusPlatformEffect() : base()
        {
        }

        protected override void OnAttached()
        {
            ...
        }

        protected override void OnDetached()
        {
        }
    }
#elif __ANDROID__
    public class FocusPlatformEffect : Microsoft.Maui.Controls.Compatibility.Platform.Android.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()
        {
        }

        // ...
    }
#elif __IOS__
    public class FocusPlatformEffect : Microsoft.Maui.Controls.Compatibility.Platform.iOS.PlatformEffect
    {
        // ...
    }
#endif
}

Register the Effect

The MauiProgram class contains a CreateMauiApp method in which the effect must be registered:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>();

        builder.ConfigureEffects(effects =>
        {
            effects.Add<FocusRoutingEffect, FocusPlatformEffect>();
        });
        return builder.Build();
    }
}

The effect is registered with the ConfigureEffects method.