Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MAUI : Customize an Entry

Tags:

maui

.net-maui

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.

like image 320
Julien Guillot Avatar asked Jan 20 '26 18:01

Julien Guillot


2 Answers

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

like image 175
ToolmakerSteve Avatar answered Jan 24 '26 11:01

ToolmakerSteve


If anyone lands on this page because of a (seemingly) simple need to change the underline color only please continue reading. The solution below takes only few lines and works for ALL MAUI controls that use highlighting / accent colors.

The accepted reply seems to be a valuable tutorial for the generic description of how to customize MAUI controls (which again corresponds to the title of the original question written possibly bit much generically). If however the color behavior is your only goal it would be a total overkill.

The MAUI implementation is based on OS personalization scheme. Unfortunately it does not seem one can override it in a general way on one place for all platforms but it is possible to control it on the platform-specific layer.

What I ended up with is modification of one file only, namely ./Platforms/Windows/App.xaml:

    <maui:MauiWinUIApplication.Resources>
        <ResourceDictionary>
            <Color x:Key="Primary">#500073</Color>
            <StaticResource x:Key="SystemAccentColorDark1" ResourceKey="Primary"/>
            <StaticResource x:Key="SystemAccentColorDark2" ResourceKey="Primary"/>
            <StaticResource x:Key="SystemAccentColorDark3" ResourceKey="Primary"/>
            <StaticResource x:Key="SystemAccentColorLight1" ResourceKey="Primary"/>
            <StaticResource x:Key="SystemAccentColorLight2" ResourceKey="Primary"/>
            <StaticResource x:Key="SystemAccentColorLight3" ResourceKey="Primary"/>
        </ResourceDictionary>
    </maui:MauiWinUIApplication.Resources>

This way your color will override the OS system one and to my best guess this will modify behavior of all controls on the given platform that uses this accent color.

Entry with customized underline color

In spite of my Windows setting show blue as the current accent color:

Windows OS Setting Colors

Originally I tried to modify the generic ./Resources/Styles/Colors.xaml file in a hope to override this behavior on ALL PLATFORMS at once but that did not seem to work. Hopefully MAUI team implements the generic behavior one day.

like image 21
Jan Zeman Avatar answered Jan 24 '26 09:01

Jan Zeman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!