I think i had read and learnt enough even tried much with Maui Handlers but there is something missing or misleading in MS documentation and in all those tutorials.
It was presented as Renderers replaced with Handlers. But if handlers cannot achieve what Renderes were exactly doing how can this be said?
So with Custom Renderers, I was able to change a custom control on a platform level. Let me give you an example,
I have a custom entry inherited from Entry, see below. I have 2 new bindable properties and i define some default values like IsNumeric, Keyboard etc.
public class CustomEntry : Entry
{
public bool IsNumeric { get; set; }
public bool IsDouble { get; set; } = true;
public int MaxDecimalDigits { get; set; }
#region Constructors
public CustomEntry()
: base()
{
Handler = new CustomEntryHandler();
MaxDecimalDigits = 2;
IsNumeric = true;
IsDouble = true;
Keyboard = Keyboard.Numeric;
// Set the events.
base.TextChanged += OnTextChanged;
}
#endregion
#region Properties
public static readonly BindableProperty FormatStringValueProperty = BindableProperty.Create("FormatString", typeof(string), typeof(CustomEntry),
defaultValue: string.Empty, defaultBindingMode: BindingMode.TwoWay);
public string FormatString
{
get => (string)GetValue(FormatStringValueProperty);
set => SetValue(FormatStringValueProperty, value);
}
public static readonly BindableProperty MaxValueProperty = BindableProperty.Create("MaxValue", typeof(double?), typeof(CustomEntry), defaultValue: null, defaultBindingMode: BindingMode.TwoWay);
public double? MaxValue
{
get => (double?)GetValue(MaxValueProperty);
set => SetValue(MaxValueProperty, value);
}
then i decide I want to customize this for Ios using a custom renderer by updating borders, padding etc. only for Ios in a custom renderer. it will look like as below
public class CustomEntryRenderer : EntryRenderer
{
#region Parent override
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
return;
Control.BorderStyle = UITextBorderStyle.None;
UpdateBorderWidth();
UpdateBorderColor();
UpdateBorderRadius();
UpdateLeftPadding();
UpdateRightPadding();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null)
return;
if (e.PropertyName == CustomEntry.BorderWidthProperty.PropertyName)
{
UpdateBorderWidth();
}
else if (e.PropertyName == CustomEntry.BorderColorProperty.PropertyName)
{
UpdateBorderColor();
}
else if (e.PropertyName == CustomEntry.BorderRadiusProperty.PropertyName)
{
UpdateBorderRadius();
}
else if (e.PropertyName == CustomEntry.LeftPaddingProperty.PropertyName)
{
UpdateLeftPadding();
}
else if (e.PropertyName == CustomEntry.RightPaddingProperty.PropertyName)
{
UpdateRightPadding();
}
}
Now i want to update this from renderer to handler in Maui. How do i achieve it? when I use a handler with mappings like below
it creates completely new Entry from native handler. it doesn't extend or update. Because i don't see my default values defined in my Cross platform level within the constructor. So how do we achieve this task? when we use handlers is it no longer crossplatform control?
public partial class CustomEntryHandler : EntryHandler
{
#region Parent override
// protected override MauiTextField CreatePlatformView() => new(VirtualView.Frame);
protected override void ConnectHandler(MauiTextField platformView)
{
platformView.KeyboardType = UIKeyboardType.NumberPad;
platformView.BorderStyle = UITextBorderStyle.None;
base.ConnectHandler(platformView);
}
#endregion
#region Utility methods
public static void UpdateBorderWidth(CustomEntryHandler handler, CustomEntry customEntry)
{
handler.PlatformView.UpdateBorder(customEntry);
}
public static void UpdateBorderColor(CustomEntryHandler handler, CustomEntry customEntry)
{
handler.PlatformView.UpdateBorder(customEntry);
}
public partial class CustomEntryHandler
{
public static PropertyMapper<CustomEntry, CustomEntryHandler> PropertyMapper = new PropertyMapper<CustomEntry, CustomEntryHandler>(EntryHandler.ViewMapper)
{
#if IOS || MACCATALYST
[nameof(CustomEntry.BorderWidth)] = UpdateBorderWidth,
[nameof(CustomEntry.BorderColor)] = UpdateBorderColor,
[nameof(CustomEntry.BorderRadius)] = UpdateBorderRadius,
[nameof(CustomEntry.LeftPadding)] = UpdateLeftPadding,
[nameof(CustomEntry.RightPadding)] = UpdateRightPadding,
#endif
};
public CustomEntryHandler() : base(PropertyMapper)
{
}
it creates completely new Entry from native handler. it doesn't extend or update. Because i don't see my default values defined in my Cross platform level within the constructor. So how do we achieve this task? when we use handlers is it no longer crossplatform control?
PS, i am using partial class approach and i want to use it because I want to keep in Maui class library creating nuget package and i dont have any MauiBuilder within a class library.
UPDATE:
I have created a test repo with demo maui project and maui class library.
https://github.com/EmilAlipiev/TestMauiHandlers
under the class library you find the Old renderers under each platform and new upgraded Handlers under handlers folders.
- So first run the Demo project in IOS without handlers. you can see the default value for text "this is default text" on the ui.
- enable ConfigureMauiHandler in MauiProgram.cs in HandlersDemoApp
3.You see that default text is empty
Reason for that is Handlers are creating a complete new Entry instead of extending the Entry defined in the shared part i believe.
If i use renderers they will be just extended with the default cross platform definitions.
last question; I have this CustomEntry in the Class library because i want to created a nuget package. How do i register handlers within the class library?