MAUI : Customize an Entry
Asked Answered
P

6

14

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.

Padova answered 27/5, 2022 at 11:21 Comment(3)
Look here ? learn.microsoft.com/en-us/dotnet/maui/user-interface/handlers/…Polyandrist
The official doc only shows simple changes. See Creating a Maui Map control for a complete example. Also https://mcmap.net/q/827455/-net-maui-custom-control-handlers-how-to-convert-content-between-virtualview-and-platformview/199364.Gregson
I have written this deep dive into handlers and I personally think it's a must read if you wanna learn the easiest way to understand how handlers work Mastering .NET MAUI: A Deep Dive into Handlers medium.com/@freakyali/…Blessing
G
32

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

Gregson answered 28/5, 2022 at 0:43 Comment(7)
Wow, that's a very concrete answer that deserves an upvote. Thanks a lot !Padova
That's a well put together answer. However, forgive me. Is there really no simpler way to do this?Amling
Excellent question! This seems more work than it was in Xamarin Forms. Perhaps that is the cost of this higher-performance renderer approach - but I hope that when more people have attempted custom renderers, it will become clearer how to make this easier. I'm guessing there need to be more/better "helper classes" built in to Maui, to handle the renderer-related complexity. (Myself, I won't be able to make a major Maui effort until Dec. or Jan. At that time, I'll see what it takes to port over all my XForms custom renderers. I'll dig deeper into Maui code, come up with a better approach.)Gregson
... Early next year, I'll make an open source library of custom handlers and their building blocks.Gregson
@Gregson Why is it that we need to map the existing properties that were already mapped? Doesn't that just create more confusion, I mean I am trying to create a custom control and now the existing properties of that control have stopped working and I have no clue why. At this point, the old XF way was better tbh at least it never broke the existing properties of our controlsBlessing
@Blessing - I am equally confused about the need to remap, and equally agree that XF was much easier to customize! I haven't been able to spend enough time yet with Maui, to work out a better solution, and suggest it to devs. (~December I will move my current client from XF to Maui.) I think I see why they've gone down the road they are on, but the current implementation is not convenient enough.Gregson
@Gregson I think i have an approach that does solve our problem check my repo here : github.com/FreakyAli/MAUI.FreakyControlsBlessing
T
6

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.

Thelmathem answered 11/3, 2023 at 8:59 Comment(0)
D
3

I'm not a fan of long-winded solutions for simple problems. Maui supports (limited) CSS, so here's what I did

  1. create a stylesheet style.css (call it what you like) and put it in the '/Resources' I don't think the location matters that much

    .bottom-border {

     border-bottom:1px solid black;
    

    }

  2. Add an entry in App.xaml to access the stylesheet

    <Application.Resources>

     <StyleSheet Source="/Resources/style.css" />
    

    </Application.Resources>

You can't mix this with existing resource dictionaries, but I didn't need them anyway, and as long as you don't get too adventurous with your CSS you can do most things, so it's not a problem. I'm not a big fan of MAUI styling, it just seems like a lot of effort for little gain, but that's just me. Check here Style apps using Cascading Style Sheets

  1. Add this ... StyleClass="bottom-border" to your element, and boom!

enter image description here

Deontology answered 17/12, 2022 at 15:56 Comment(0)
O
1

I’d like to share some guidance on this topic. Explaining the full implementation of a custom border for an Entry control in MAUI can be quite extensive.

Rather than detailing all the steps here, I recommend a well-written article that walks you through the entire process step by step, you can draw border using or using SkiaSharp for different result.

  1. Initialize the MAUI project and add SkiaSharp packages.

  2. Create a custom entry control (e.g., EntryLabel) using XAML and C#.

  3. Customize the Entry’s appearance using EntryHandler to remove default platform borders.

  4. Use SkiaSharp to draw a custom bottom border.

  5. Integrate everything in your custom control, ensuring the SkiaSharp canvas handles the border-drawing logic.

    (MAUI Library Part 1) Create a Custom Entry using SkiaSharp

Here the expected result.

Result of MAUI Entry with custom border

Oration answered 18/8, 2024 at 11:2 Comment(0)
K
0

Although this question is a bit old, I figured it might be useful for some .NET MAUI developers to know there are color accent definitions for Android specifically. Android color accents are defined within this specific resource directory:

Android platform resource directory

These color definitions are responsible for the entry border color and probably a few other color accents, specifically for Android devices. Unfortunately this does not change the border thickness, radius or any other shape styling. Usually changing the color is enough for me though. The thick border makes the entry more accessible in my opinion.

Please note: Contrary to the color definitions for your entire project, the definitions for Android are written in lower case and not capitalized. You also have to use name instead of x:Key attributes or it might throw an exception about value converters not working.

So...

instead of <Color x:Key="Primary">#00FFFF</Color>
you'll define colors like <color name="colorPrimary">#00FFFF</color>

Kuopio answered 28/6, 2023 at 11:12 Comment(0)
A
-1

The accepted answer is really complicated. How about using Grid, Frame, Entry and BoxView like this:

<Grid>
    <Frame Padding="0" />
    <Entry
        Margin="5,0,5,0"
        Placeholder="Put some text here..."
    />
    <BoxView
        HeightRequest="1"
        Color="LightGray"
        VerticalOptions="End"
        Margin="0,0,0,5"
    />
</Grid>

Tweak as needed of course

Alwitt answered 28/10, 2022 at 21:27 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.