Search code examples
maui.net-maui

MAUI : Customize an Entry


I'm new to MAUI with basic knowledge on Xamarin.Forms

I would like to add a bottom border (and border tickness) to an Entry control in MAUI.

On Xamarin.Forms, we had to create a Custom Control and then a Renderer for each platforms.

I did research on the internet before posting this message. It concerns the Handlers and I have the impression that it only allows basic modifications (changing the color of the background, etc...).

I'm a bit confused with all this information, if anyone could enlighten me I would appreciate it.


Solution

  • Customize specific control instances shows a trivial example of a custom Entry, that only customizes some properties per platform.

    I've started to create a .Net Maui advanced custom Entry example. See that repo for the implementation so far.

    Status:

    • Demonstrates underline color and thickness on Windows.
    • Has beginnings of an underline on Android.

    Limitations (current):

    • On Windows, some Entry properties need to be mapped to the contained TextBox.
    • Have not yet tested Entry events, to see if they need any special handling.
    • The Android underline has no control over its thickness or color.
    • No implementation on platforms other than Windows or iOS.

    If you wish to extend this farther, google xamarin forms customize entry renderer for examples of platform-specific code. Hopefully I've shown enough to give a sense of how/where to add such code.


    At this time, doing an advanced example seemed to be MORE work than the corresponding Xamarin Forms "custom renderers".

    REASONS:

    • The need to fit into Maui's handler mapping scheme. Probably just need advanced documentation and examples.
    • Difficulty extending existing EntryHandler, due to types needed by IEntry and IEntryHandler. HOW OVERRIDE type of PlatformView??
    • Difficulty replicating what existing handler does (in order to make slight changes), because some Extensions used by built-in handlers are "internal", so had to copy those files. Which referred to other files. That I then copied. And made some changes to avoid ambiguity conflicts with the existing Extensions.

    TBD: Perhaps there is a way to avoid the complications I encountered.
    ALSO there may be code I copied, that could be omitted.


    These are the steps that need to be done:

    1. Define class MyEntry : Entry with desired additional properties.
    2. Define class MyEntryHandler to render to native UI object(s).
    3. AddHandler in MauiProgram.

    1. Define class MyEntry : Entry with desired additional properties.

    Here, we add UnderlineColor and UnderlineThickness.

    public class MyEntry : Entry
    {
        /// <summary>
        /// Color and Thickness of bottom border.
        /// </summary>
        public static BindableProperty UnderlineColorProperty = BindableProperty.Create(
                nameof(UnderlineColor), typeof(Color), typeof(MyEntry), Colors.Black);
        public Color UnderlineColor
        {
            get => (Color)GetValue(UnderlineColorProperty);
            set => SetValue(UnderlineColorProperty, value);
        }
    
        public static BindableProperty UnderlineThicknessProperty = BindableProperty.Create(
                nameof(UnderlineThickness), typeof(int), typeof(MyEntry), 0);
        public int UnderlineThickness
        {
            get => (int)GetValue(UnderlineThicknessProperty);
            set => SetValue(UnderlineThicknessProperty, value);
        }
    
        public MyEntry()
        {
        }
    }
    

    2. Define class MyEntryHandler to render to native UI object(s).

    This is done with a partial class. One part is cross-platform, then need another part for each platform that you implement.

    In my repo, find MyEntryHandler.cs, Windows/MyEntryHandler.Windows.cs, and Android/MyEntryHandler.Android.cs.

    MyEntryHandler.cs:

    This contains the "Mapper" for MyEntryHandler.

        // Cross-platform partial of class. See Maui repo maui\src\Core\src\Handlers\Entry\EntryHandler.cs
        public partial class MyEntryHandler : IMyEntryHandler //: EntryHandler
        {
            // static c'tor.
            static MyEntryHandler()
            {
                // TBD: Fill MyMapper here by copying from Entry.Mapper, then add custom ones defined in MyEntry?
            }
    
            //public static IPropertyMapper<IEntry, IEntryHandler> MyMapper => Mapper;
            public static IPropertyMapper<IEntry, MyEntryHandler> MyMapper = new PropertyMapper<IEntry, MyEntryHandler>(ViewMapper)
            {
                // From Entry.
                [nameof(IEntry.Background)] = MapBackground,
                [nameof(IEntry.CharacterSpacing)] = MapCharacterSpacing,
                [nameof(IEntry.ClearButtonVisibility)] = MapClearButtonVisibility,
                [nameof(IEntry.Font)] = MapFont,
                [nameof(IEntry.IsPassword)] = MapIsPassword,
                [nameof(IEntry.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
                [nameof(IEntry.VerticalTextAlignment)] = MapVerticalTextAlignment,
                [nameof(IEntry.IsReadOnly)] = MapIsReadOnly,
                [nameof(IEntry.IsTextPredictionEnabled)] = MapIsTextPredictionEnabled,
                [nameof(IEntry.Keyboard)] = MapKeyboard,
                [nameof(IEntry.MaxLength)] = MapMaxLength,
                [nameof(IEntry.Placeholder)] = MapPlaceholder,
                [nameof(IEntry.PlaceholderColor)] = MapPlaceholderColor,
                [nameof(IEntry.ReturnType)] = MapReturnType,
                [nameof(IEntry.Text)] = MapText,
                [nameof(IEntry.TextColor)] = MapTextColor,
                [nameof(IEntry.CursorPosition)] = MapCursorPosition,
                [nameof(IEntry.SelectionLength)] = MapSelectionLength,
                // From MyEntry
                [nameof(MyEntry.UnderlineThickness)] = MapUnderlineThickness
            };
    
            // TBD: What is this for? Cloned one on Entry.
            private static void MapUnderlineThickness(MyEntryHandler arg1, IEntry arg2)
            {
            }
    
    
            public MyEntryHandler() : base(MyMapper)
            {
            }
    

    I did not yet create minimal partial classes in ALL platform folders. In the repo's cross-platform MyEntryHandler, you'll see code inside #if WINDOWS. The intent is that this NOT need to be wrapped in #if. You'll also see a lot of commented out code; this was so I could see what methods needed to be implemented on each platform.

    MyEntryHandler.Windows.cs:

    The essence is CreatePlatformView(). On Windows, I chose to implement as a Border (with zero on all sides except bottom) containing a TextBox.

            protected override PlatformView CreatePlatformView()
            {
                var myentry = VirtualView as MyEntry;
    
                var textbox = new MauiPasswordTextBox
                {
                    // From EntryHandler.
                    IsObfuscationDelayed = s_shouldBeDelayed
    
                    // TODO: pass some entry properties through to textbox?
                };
    
                MauiColor color = myentry != null
                        ? myentry.UnderlineColor
                        : MyEntry.UnderlineColorProperty.DefaultValue as MauiColor;
                int thickness = myentry != null
                        ? myentry.UnderlineThickness
                        : (int)MyEntry.UnderlineThicknessProperty.DefaultValue;
    
                var border = new Border
                {
                    Child = textbox,
                    BorderBrush = color.ToPlatform(),
                    BorderThickness = new Thickness(0, 0, 0, thickness)
                };
    
    
                return border;
            }
    

    There are many other lines of the Windows Handler. These are all copied from Maui source. TBD which (if any) of these are needed. If I'd figured out how to simply inherit from Maui's EntryHandler, those would not be needed. But there were type conflicts when I inherited.


    3: AddHandler in MauiProgram.

    MauiProgram.cs

        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureMauiHandlers(handlers =>
                {
                    handlers.AddHandler(typeof(MyEntry), typeof(MyEntryHandler));
                })
            ...
    

    You'll see other classes added to the Maui project in repo. These are copied from Maui sources.

    These other classes are referenced by the classes mentioned above.

    Hopefully most of the other classes go away, once this topic is better understood.


    On Windows, AppShell + MainPage with two MyEntrys. One is underlined and colored.

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:myviews="clr-namespace:MauiCustomEntryHandler"
                 x:Class="MauiCustomEntryHandler.MainPage">
                 
        <ScrollView>
            <VerticalStackLayout 
                    WidthRequest="500" HeightRequest="400"
                    Spacing="25" Padding="30,0" BackgroundColor="LightBlue"
                    HorizontalOptions="Center" VerticalOptions="Center">
                <Label Text="Hello, Maui!" FontSize="24" HorizontalOptions="Center" />
                <myviews:MyEntry Text="test" FontSize="20" UnderlineThickness="8"
                     UnderlineColor="Purple" BackgroundColor="HotPink" />
                <myviews:MyEntry UnderlineThickness="0" BackgroundColor="LightGray" />
            </VerticalStackLayout>
        </ScrollView>
     
    </ContentPage>
    
    

    enter image description here