Xamarin Forms Migration to Uno Platform: Effects and Alternative Approaches

In our previous post on Xamarin Forms migration, we explored the use of custom renderers, which enable the integration of native controls within Xamarin Forms. Building upon that, this article delves into the concept of Effects, a valuable tool for making smaller customizations to native controls. We will also explore how Uno Platform while having no direct equivalent to Effects, offers alternative approaches to achieve similar functionality.

What’s so special about Effects?

An Effect can be applied to a control in XAML to add some specific look and feel or behavior change to the control. Usually this will require platform specific implementations which change the native controls. Cleverly though, Xamarin Forms has a method of registering the platform implementation with a unique name so that you apply the effect once and Xamarin Forms will apply the correct native effect for you. If there is no corresponding effect on the current platform, then nothing happens – you don’t have to worry about an error occurring where you’ve not added the functionality. 

A common Effect across Xamarin Forms projects is one to remove the default color underline on the Entry control on Android. It’s such a common scenario that it’s been added to the Xamarin Community Toolkit, but in the sample code we’ve recreated a full Effect to show how it all fits together. 

A class is created in shared code which inherits from RoutingEffect. This is what is inserted into the XAML. We must add an assembly wide ResolutionGroupName attribute to define a unique identifier for all our custom effects. Often this will be the same name as the namespace the effect sits in. 

In each native platform project we then create a class which inherits from PlatformEffect. The customization is performed within two required methods – OnAttached and OnDetached. This assembly must also have a matching ResolutionGroupName attribute. Finally, an ExportEffect attribute registers this specific platform effect with Xamarin Forms. The complete sample code shows how this all fits together and you can see the difference between controls with and without this effect applied. 

Xamarin Forms Effects Equivalent in Uno Platform

Uno Platform is based on the WinUI API and as such doesn’t have an equivalent concept to Effects. It does, however, have a couple of alternative approaches. The first is very powerful templating support which allows you to dramatically change the control appearance. Uno doesn’t use the native underline in the Android EditText control, instead the default template is either a Fluent (or Material) implementation which does include a rounded border and a thinner underline on all platforms. 

Xamarin Forms Entry control with and without the NoUnderlineEffect

Control Templates

A ControlTemplate allows you to replace the visual tree for a specific control type and it can include other controls. To create a custom TextBox with a new type of border but without the underline we can therefore create a template and apply it to TextBox controls using a Style. Any TextBoxes with this Style specified will use the template to define their appearance and you can create any number of different templates for use in different parts of your app. We’ll call our Style RoundedTextBox and define a rounded border, inside of which there is a ContentControl which hosts the native control with none of the platform adornments. The name for this inner ContentControl is important because Uno will use this to manipulate the native control. We can look at the current templates in the Uno Platform source to see what we need to implement to replace it. 

				
					<Style x:Key="RoundedTextBox"  TargetType="TextBox"> 
        <Setter Property="Template"> 
            <Setter.Value> 
                <ControlTemplate TargetType="TextBox"> 
                    <Border CornerRadius="6" BorderBrush="{TemplateBinding Foreground}" Background="Wheat" BorderThickness="2"> 
<ContentControl x:Name="ContentElement" />                    </Border> 
                </ControlTemplate> 
            </Setter.Value> 
        </Setter> 
    </Style> 
				
			

As with any other XAML resource you can define this style either locally at a page (or control) level or in the shared AppResources.xaml to make it available throughout the app. There are some key points to take from this XAML. Both the Style and the ControlTemplate must set the TargetType property to specify the type of control they apply to. It’s not shown here but your style can contain any other setters required alongside the template. The template contains the XAML controls which will make up the control and will have a Border or Grid or similar container control and other elements inside that.  

The Content

You can see that there is no TextBox in this template – the inner ContentControl represents the actual TextBox part of the control. If you recall the previous article on migrating from Xamarin Forms Renderers, Uno has helper methods to create a ContentControl that hosts the native control and manages layout. We are just using it to display the text and text editing functionality, and the Border in our template defines the appearance of the control.  

To illustrate the “raw” control, the sample also contains a NakedTextBox style which templates the control with just the ContentElement. The control has no adornments and is just the raw EditText control. 

Customize Element Properties with Template Binding

Depending on how you want to control the properties of the various elements in the template, you can use TemplateBinding so that the properties will take their values from the control as defined in your page or control. In real-world use we would probably use TemplateBinding for more properties, but for simplicity here we just use it on the BorderBrush property of the Border. This means that value is passed through, and it fully supports data-binding. As far as the control behaves to our consuming page it is just another TextBox. We have hard-coded a few properties such as using Wheat as a background so that it shows up against the default white. 

You can use template binding for different properties as long as they are the same type. For example, we can bind the BorderBrush of the Border to the Foreground property of the TextBox because they are both Brush types. 

You can see the difference between different templated controls here with the NakedTextBox and RoundedTextBox templates:

Two templated TextBox controls

Using ControlTemplate

The only limit to this approach is that the ControlTemplate can only be used on properties which are exposed by the primitive WinUI controls. Another feature of Effects in Xamarin Forms is that they can be used to run code on attach and detach from the control. This means that there is an ability to call methods and run other code in platform specific code rather than just changing properties. 

Getting in Control

The good news is that you can do more with Uno, but you have to use conditional compilation to “hide” each of the platform implementations from each other. In Xamarin Forms, you can create a sub-class of a control and customize this while inheriting all the functionality from the standard control. While controls in Uno Platform are not sealed, those in WinUI are, and so to maintain cross-platform compatibility, you cannot do the same. Instead, we can create a helper class and add functionality either by adding attached properties or an extension method which takes the required type. 

To test this technique, we’re going to implement a native feature of the Android EditText control (this is what an Uno Platform TextBox uses internally). The control supports an Error property which will show an error overlay below the control – you can use this to indicate data-validation issues. Let’s create an attached property to apply this to any TextBox (on Android – on every other platform we’ll do nothing). 

To understand how this is possible, we need to know how to get at the native EditText control. We already know that the default ControlTemplate for TextBox contains a ContentControl named “ContentElement”. Given the instance of the control we can call GetTemplateChild with this unique name and retrieve the ContentControl element. This control contains the native control in its Content property. But here is where we need to be careful when supporting multiple platforms because on iOS this will be a UITextView for example. As mentioned before, we can use conditional compilation to add platform specific code. Any code which references Android types should be within an #if ANDROID block. Likewise, any usings for platform specific namespaces must be wrapped in #if directives. 

Once we have our EditText instance we can get/set any properties and call any methods to override its behavior. Again, we only need to do this for functionality which can’t be set with a template because our objective should be to avoid writing platform code unless it is necessary. 

Our sample project contains a TextBoxErrorHelper class. This defines our new attached property and the change handler method which is called whenever the property value is changed. Currently there is only an implementation for Android, so the code looks like this

				
					public static void OnErrorTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    TextBox t = (TextBox)d; 
#if ANDROID 
    var contentElement = (ContentControl)t.GetTemplateChild("ContentElement"); 
    if (contentElement != null) 
    { 
        var editText = (Android.Widget.EditText)contentElement.Content; 
        editText.Error = (string)e.NewValue; 
    } 
#endif 
} 
				
			

The code gets the ContentControl defined in the template, accesses the inner native EditText control, then sets its Error property which defines the message to display. Setting this Error property to null removes the error warning and associated message. 

Because this helper class exists in the same namespace as the app page the namespace is already defined in XAML as local, but otherwise we would add an xmlns attribute to the page to point to the required namespace. Once done we add this attached property to any TextBox control to add this feature. Note that because we are using x:Bind to set the value we need to specify Mode=OneWay to pass through changes in this property because x:Bind defaults to OneTime binding so is set only once. 

				
					<TextBox x:Name="ErrorTextBox" FontSize="18" Text="Text with error" local:TextBoxErrorHelper.ErrorText="{x:Bind ErrorText, Mode=OneWay}"/> 
				
			

On an Android device, the control shows an error warning and when selected displays the error text we provided: – 

Next Steps

We’ve looked at modifying native controls with Uno Platform in a similar way to Effects in Xamarin Forms. You can achieve this via either control templates, or by accessing the native control in code and using conditional compilation to provide different code for different target platforms. 

The sample code projects used in this post is all available in this GitHub repository. 

To upgrade to the latest release of Uno Platform, please update your packages to 4.9 via your Visual Studio NuGet package manager! If you are new to Uno Platform, following our official getting started guide is the best way to get started. (5 min to complete)

Tags:

Uno Platform 5.2 LIVE Webinar – Today at 3 PM EST – Watch