# Handler Customization / Tùy Biến Handler ## Overview / Tổng Quan .NET MAUI sử dụng **Handlers** thay vì Custom Renderers (Xamarin.Forms legacy). Handlers cung cấp cách tiếp cận hiện đại, hiệu quả hơn để tùy chỉnh native controls. ``` ┌─────────────────────────────────────────────────────────────┐ │ HANDLER ARCHITECTURE │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ MAUI View │ ─── Handler ───► │ Native View │ │ │ │ (Entry) │ │ (EditText/UITxt) │ │ │ └──────────────┘ └──────────────────┘ │ │ │ │ │ │ │ PropertyMapper │ │ │ └──────────────────────────────────►│ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Customization Levels / Các Cấp Độ Tùy Biến ### Level 1: XAML Styles (Ưu tiên cao nhất) Dùng cho thay đổi properties cơ bản: ```xml ``` ### Level 2: ControlTemplate Dùng khi cần thay đổi cấu trúc visual: ```xml ``` ### Level 3: Handlers (Khi cần Native API) Dùng khi XAML không đủ (bỏ underline, đổi cursor color, etc.): ```csharp // ⚠️ CHỈ dùng khi cần thiết EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) => { #if ANDROID handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent); #elif IOS handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; #endif }); ``` --- ## Handler Mapping Methods ### AppendToMapping (Recommended) Thêm customization **sau** default mapping: ```csharp public static class EntryHandlerExtensions { public static void ConfigureBrandEntry(this MauiAppBuilder builder) { EntryHandler.Mapper.AppendToMapping("BrandEntry", (handler, view) => { #if ANDROID // Bỏ underline mặc định handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent); // Đổi màu cursor if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Q) { handler.PlatformView.SetTextCursorDrawable(null); } #elif IOS // Bỏ border handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; #endif }); } } // MauiProgram.cs public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder.ConfigureBrandEntry(); return builder.Build(); } ``` ### PrependToMapping Thêm customization **trước** default mapping: ```csharp // Ít dùng - chỉ khi cần override trước EntryHandler.Mapper.PrependToMapping("BeforeDefault", (handler, view) => { // Code chạy TRƯỚC default mapping }); ``` ### ModifyMapping (Nguy hiểm) Thay thế hoàn toàn một property mapping: ```csharp // ⚠️ NGUY HIỂM - có thể break default behavior EntryHandler.Mapper.ModifyMapping(nameof(Entry.Text), (handler, view, action) => { // Thay thế hoàn toàn cách xử lý Text property action?.Invoke(handler, view); }); ``` --- ## Complete Handler Examples ### BrandEntry Handler (Bỏ Underline) ```csharp // Handlers/BrandEntryHandler.cs namespace MyApp.Handlers; public static class BrandEntryHandler { /// /// Configure Entry để bỏ underline mặc định trên Android /// và bỏ border trên iOS /// public static void Configure() { Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping( "BrandEntry", (handler, view) => { #if ANDROID ConfigureAndroid(handler); #elif IOS || MACCATALYST ConfigureIOS(handler); #endif }); } #if ANDROID private static void ConfigureAndroid( Microsoft.Maui.Handlers.EntryHandler handler) { // Bỏ background/underline mặc định handler.PlatformView.SetBackgroundColor( Android.Graphics.Color.Transparent); // Optional: Select all on focus handler.PlatformView.SetSelectAllOnFocus(true); } #endif #if IOS || MACCATALYST private static void ConfigureIOS( Microsoft.Maui.Handlers.EntryHandler handler) { // Bỏ border handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; // Optional: Clear button handler.PlatformView.ClearButtonMode = UIKit.UITextFieldViewMode.WhileEditing; } #endif } ``` ### BrandPicker Handler (Custom Arrow) ```csharp namespace MyApp.Handlers; public static class BrandPickerHandler { public static void Configure() { Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping( "BrandPicker", (handler, view) => { #if ANDROID // Bỏ underline handler.PlatformView.SetBackgroundColor( Android.Graphics.Color.Transparent); #elif IOS // Custom styling handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; #endif }); } } ``` ### BrandEditor Handler (Multi-line Text) ```csharp namespace MyApp.Handlers; public static class BrandEditorHandler { public static void Configure() { Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping( "BrandEditor", (handler, view) => { #if ANDROID // Bỏ background handler.PlatformView.SetBackgroundColor( Android.Graphics.Color.Transparent); // Set max lines handler.PlatformView.SetMaxLines(5); #elif IOS // Customize TextView handler.PlatformView.Layer.BorderWidth = 0; #endif }); } } ``` --- ## Cross-Platform Partial Classes Khi code native dài, tách thành Partial Classes: ### Shared Interface ```csharp // Services/IBrandingService.cs namespace MyApp.Services; public interface IBrandingService { void ConfigureEntry(object platformView); void ConfigurePicker(object platformView); } ``` ### Partial Implementation ```csharp // Services/BrandingService.cs (shared) namespace MyApp.Services; public partial class BrandingService : IBrandingService { public partial void ConfigureEntry(object platformView); public partial void ConfigurePicker(object platformView); } ``` ```csharp // Platforms/Android/Services/BrandingService.Android.cs namespace MyApp.Services; public partial class BrandingService { public partial void ConfigureEntry(object platformView) { if (platformView is AndroidX.AppCompat.Widget.AppCompatEditText editText) { editText.SetBackgroundColor(Android.Graphics.Color.Transparent); editText.SetSelectAllOnFocus(true); } } public partial void ConfigurePicker(object platformView) { if (platformView is AndroidX.AppCompat.Widget.AppCompatEditText editText) { editText.SetBackgroundColor(Android.Graphics.Color.Transparent); } } } ``` ```csharp // Platforms/iOS/Services/BrandingService.iOS.cs namespace MyApp.Services; public partial class BrandingService { public partial void ConfigureEntry(object platformView) { if (platformView is UIKit.UITextField textField) { textField.BorderStyle = UIKit.UITextBorderStyle.None; textField.ClearButtonMode = UIKit.UITextFieldViewMode.WhileEditing; } } public partial void ConfigurePicker(object platformView) { if (platformView is UIKit.UITextField textField) { textField.BorderStyle = UIKit.UITextBorderStyle.None; } } } ``` --- ## MauiProgram.cs Integration ```csharp using MyApp.Handlers; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() .UseMauiCommunityToolkit() .ConfigureFonts(fonts => { fonts.AddFont("Inter-Regular.ttf", "InterRegular"); fonts.AddFont("Inter-Bold.ttf", "InterBold"); }); // ✅ Configure all brand handlers ConfigureBrandHandlers(); return builder.Build(); } private static void ConfigureBrandHandlers() { BrandEntryHandler.Configure(); BrandPickerHandler.Configure(); BrandEditorHandler.Configure(); } } ``` --- ## Common Mistakes / Lỗi Thường Gặp ### ❌ DON'T: Use Custom Renderers ```csharp // ❌ LEGACY - Không dùng nữa [assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))] public class CustomEntryRenderer : EntryRenderer { // Old Xamarin.Forms approach } ``` ### ✅ DO: Use Handlers ```csharp // ✅ MODERN - .NET MAUI approach EntryHandler.Mapper.AppendToMapping("Custom", (h, v) => { }); ``` ### ❌ DON'T: Use `#if` everywhere ```csharp // ❌ Code pollution public void Configure() { #if ANDROID // 50 lines of Android code #elif IOS // 50 lines of iOS code #elif WINDOWS // 50 lines of Windows code #endif } ``` ### ✅ DO: Use Partial Classes ```csharp // ✅ Clean separation // Services/MyService.cs public partial class MyService { public partial void DoPlatformWork(); } // Platforms/Android/Services/MyService.Android.cs public partial class MyService { public partial void DoPlatformWork() { // Android-specific } } ``` --- ## Quick Reference | Task | Method | |------|--------| | Thay đổi màu/font | XAML Styles | | Thay đổi cấu trúc UI | ControlTemplate | | Bỏ underline Entry | Handler + AppendToMapping | | Đổi cursor color | Handler + Native API | | Thêm clear button (iOS) | Handler + UITextField | | Custom dropdown arrow | Handler + Native Drawable | --- ## Related Guidelines - [Resource Architecture](./resource-architecture.md) - XAML Resources - [Accessibility Rules](./accessibility-rules.md) - WCAG compliance