'What will happen to custom controls and Renderers in .net maui?

In my Current Xamarin.Forms projects I have created many Renderers for different requirements. Now in September maui is coming and we have to migrate our projects to that. So in that what will happen to Renderers? Is it going to work as it is or we will not need that Renderers?



Solution 1:[1]

In .NET MAUI the concept of renderers will go away and is replaced by the handler architecture. Renderers were too tightly coupled to the actual controls and the supported platforms, on top of that (or maybe because of that) they were slow as they relied on reflection etc.

A big part of why .NET MAUI is so exciting is that this whole architecture will change. Now, instead of the renderers there will be handlers which basically handle one property at a time and there is an abstraction added between the handler and the control. This is a big deal because now backends (think iOS, Android, but also SkiaSharp or others) can be implemented more easily.

As to your question: there are different migration paths possible. You can reuse your current existing custom renderers as they are. There should be very minimal code changes. The Xamarin.Forms/.NET MAUI team has worked hard to implement a compatibility layer to pull that off. However, you will get limited benefits from switching to .NET MAUI, but it will buy you some time to rewrite those renderers and still be able to release your app. Although there should be plenty of time, Xamarin.Forms is still supported until November 2022.

Without going into too much technical details, reusing your current renderer might look like this in your Startup.cs class in your .NET MAUI project (full code here):


public void Configure(IAppHostBuilder appBuilder)
{
    appBuilder
        .UseMauiApp<App>()
        .ConfigureMauiHandlers(handlers =>
              {
#if __ANDROID__
        handlers.AddCompatibilityRenderer(typeof(Label), typeof(MAUICustomRendererSample.Platforms.Android.MyLabelRenderer));
#endif
        })
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        });
}

Then, of course, there is still the possibility to override the handler behavior. There are multiple ways to do this depending on your needs. To just override one property, that is now much easier than in the renderer era. For instance, have a look at the code below to just change the background color of a Button (full code here):

public void Configure(IAppHostBuilder appBuilder)
{
    appBuilder
        .UseMauiApp<App>()
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        });

    #if __ANDROID__
                Microsoft.Maui.Handlers.ButtonHandler.ButtonMapper["MyCustomization"] = (handler, view) =>
                {
                    handler.NativeView.SetBackgroundColor(Android.Graphics.Color.Green);
                };
    #endif
}

To create a whole custom control that effort is about the same only the approach is different. More samples of transitions from Forms to .NET MAUI can be found here in this example from Javier who has been working on Forms and now .NET MAUI. Most of the other things (behaviors, effects, etc.) are not more than replacing some namespaces in your code and it all should work.

As a cherry on top there is a Upgrade Assistant that will help you with the most tasks of changing your project and renaming namespaces etc. More info on that can be found in this months Community Standup session.

Depending on the project size and time/budget you have available I would personally first port everything with the Forms compatibility layer. That should require the least amount of effort. Check if everything still works as intended and you should be able to move forward with that. After that, one by one start replacing your renderers with the new handler structure.

Lastly; be aware that all the third-party libraries that you may be using also all need to be ready for .NET 6 and/or .NET MAUI so make sure to check those before you start upgrading.

Other good resource to keep your eye on is the Docs that are being worked on.

Solution 2:[2]

In.Net Maui here is the complete custom handlers for the record reference.

Create Interface base IView //ICustomButton.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomButtonLib.Controls
{
    public interface ICustomButton : IView
    {
        public string Text { get; }
        public string TexColor { get; }
    }
}

CustomButton BindableProperty //CustomButton.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomButtonLib.Controls
{
    public class CustomButton : View, ICustomButton
    {
        public static readonly BindableProperty TextProperty =
            BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomButton), string.Empty);
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        public static readonly BindableProperty TextColorProperty =
           BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(CustomButton), Colors.ForestGreen);
        public Color TextColor
        {
            get { return (Color)GetValue(TextProperty); }
            set { SetValue(TextColorProperty, value); }
        }
    }


}

Create a partial CustomButtonHandler //CustomButtonHandler.cs

using CustomButtonLib.Controls;
using Microsoft.Maui.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomButtonLib.Handlers
{
    public partial class CustomButtonHandler
    {
        public static PropertyMapper<ICustomButton, CustomButtonHandler> CustomButtonMapper = new PropertyMapper<ICustomButton, CustomButtonHandler>(ViewHandler.ViewMapper)
        {
            [nameof(ICustomButton.Text)] = MapText,
            [nameof(ICustomButton.TextColor)] = MapTextColor,
        };

        public CustomButtonHandler() : base(CustomButtonMapper)
        {

        }
        public CustomButtonHandler(PropertyMapper mapper = null) : base(mapper ?? CustomButtonMapper)
        {

        }

    }
}

.NetStandard partial class //CustomButtonHandler.Standard.cs

using CustomButtonLib.Controls;
using Microsoft.Maui.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CustomButtonLib.Handlers
{
    public partial class CustomButtonHandler : ViewHandler<ICustomButton, object>
    {
        protected override object CreatePlatformView()
        {
            throw new NotImplementedException();
        }
        public static void MapText(CustomButtonHandler handler, ICustomButton iCustomButton) { }

        public static void MapTextColor(CustomButtonHandler handler, ICustomButton iCustomButton) { }
    }
}

Create specific handler in Platform Folder (Ex. Windows) //CustomButtonHandler.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


using Microsoft.Maui.Handlers;
using Microsoft.UI.Xaml;

namespace CustomButtonLib.Handlers
{
    public partial class CustomButtonHandler : ViewHandler<ICustomButton, FrameworkElement>
    {
        protected override FrameworkElement CreatePlatformView()
        {
            return new Microsoft.UI.Xaml.Controls.Button();
        }
        protected override void ConnectHandler(Microsoft.UI.Xaml.Controls.Button nativeView)
        {
            //Subscribe events

            base.ConnectHandler(nativeView);
        }

        protected override void DisconnectHandler(Microsoft.UI.Xaml.Controls.ButtonnativeView)
        {
            //Unsubcribe events
            base.DisconnectHandler(nativeView);
        }

        public static void MapText(CustomButtonHandler handler, ICustomButton iCustomButton)
        {
             public static void MapText(CustomButtonHandler handler, ICustomButton iCustomButton)
        { 
            if (handler != null && handler.PlatformView!=null && handler.PlatformView is Microsoft.UI.Xaml.Controls.Button bttn) 
                bttn.Content = iCustomButton.Text;
        }
        }

        [MissingMapper] //Add this to indicate if not available on this platform.
        public static void MapTextColor(CustomButtonHandler handler, ICustomButton iCustomButton) { }

    }
}

Continue On Other Platforms for successful build.

Optional => Add Project sdk ItemGroups, so no need to Add Platform Directive. (Ex. #if Windows).

<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-ios')) != true ">
        <Compile Remove="Platforms\iOS\*.cs" />
        <None Include="Platforms\iOS\*.cs" />
    </ItemGroup>

    <ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-maccatalyst')) != true ">
        <Compile Remove="Platforms\MacCatalyst\*.cs" />
        <None Include="Platforms\MacCatalyst\*.cs" />
    </ItemGroup>

    <ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-android')) != true ">
        <Compile Remove="Platforms\Android\*.cs" />
        <None Include="Platforms\Android\*.cs" />
    </ItemGroup>

    <ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
        <Compile Remove="Platforms\Windows\*.cs" />
        <None Include="Platforms\Windows\*.cs" />
    </ItemGroup>

    <ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-ios')) == true OR $(TargetFramework.StartsWith('net6.0-maccatalyst')) == true OR $(TargetFramework.StartsWith('net6.0-android')) == true OR $(TargetFramework.Contains('-windows')) == true">
        <Compile Remove="**\*.Standard.cs" />
        <None Include="**\*.Standard.cs" />
        <Compile Remove="**\Standard\**\*.cs" />
        <None Include="**\Standard\**\*.cs" />
    </ItemGroup>



    <!-- ANDROID -->
    <PropertyGroup Condition="$(TargetFramework.StartsWith('net6.0-android'))">
        <DefineConstants>$(DefineConstants);MONOANDROID</DefineConstants>
    </PropertyGroup>

    <!-- IOS -->
    <PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0-ios' ">
        <DefineConstants>$(DefineConstants);IOS</DefineConstants>
    </PropertyGroup>

    <!-- MACCATALYST -->
    <PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0-maccatalyst' ">
        <DefineConstants>$(DefineConstants);MACCATALYST;IOS</DefineConstants>
    </PropertyGroup>

    <!-- WINDOWS -->
    <PropertyGroup Condition="$(TargetFramework.Contains('-windows')) == true ">
        <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
        <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
        <DefineConstants>WINDOWS;$(DefineConstants)</DefineConstants>
    </PropertyGroup>

Last Register Custom Button on Builder Services => Extensions

 public static MauiAppBuilder ConfigureLibrary(this MauiAppBuilder builder)
        {
            builder
                .ConfigureMauiHandlers(handlers =>
                {
                    handlers.AddLibraryHandlers();
                });
            return builder;
        }

        

        public static IMauiHandlersCollection AddLibraryHandlers(this IMauiHandlersCollection handlers)
        {
            handlers.AddTransient(typeof(CustomButton), h => new CustomButtonHandler());
            
            return handlers;
        }
// Usage .ConfigureLibrary()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gerald Versluis
Solution 2