Personalización de controles con controladores

Browse sample.Examina la muestra

Los controladores se pueden personalizar para aumentar la apariencia y el comportamiento de un control multiplataforma más allá de la personalización posible a través de la API del control. Esta personalización, que modifica las vistas nativas para el control multiplataforma, se logra modificando el asignador para un controlador con uno de los métodos siguientes:

  • PrependToMapping, que modifica el asignador de un controlador antes de que se hayan aplicado las asignaciones de controles MAUI de .NET.
  • ModifyMapping, que modifica una asignación existente.
  • AppendToMapping, que modifica el asignador de un controlador después de aplicar las asignaciones de controles MAUI de .NET.

Cada uno de estos métodos tiene una firma idéntica que requiere dos argumentos:

  • Clave basada en string. Al modificar una de las asignaciones proporcionadas por .NET MAUI, se debe especificar la clave usada por .NET MAUI. Los valores de clave usados por las asignaciones de controles MAUI de .NET se basan en nombres de interfaz y propiedades, por ejemplo nameof(IEntry.IsPassword). Las interfaces y sus propiedades, que abstraen cada control multiplataforma se pueden encontrar aquí. Este es el formato de clave que se debe usar si desea que la personalización del controlador se ejecute cada vez que cambie una propiedad. De lo contrario, la clave puede ser un valor arbitrario que no tiene que corresponder con el nombre de una propiedad expuesta por un tipo. Por ejemplo, MyCustomization se puede especificar como clave, con cualquier modificación de vista nativa que se realice como personalización. Sin embargo, una consecuencia de este formato de clave es que la personalización del controlador solo se ejecutará cuando el asignador del controlador se modifique por primera vez.
  • Un elemento Action que representa el método que realiza la personalización del controlador. Action especifica dos argumentos:
    • Argumento handler que proporciona una instancia del controlador que se va a personalizar.
    • Argumento view que proporciona una instancia del control multiplataforma que implementa el controlador.

Importante

Las personalizaciones del controlador son globales y no tienen como ámbito una instancia de control específica. La personalización del controlador puede producirse en cualquier lugar de la aplicación. Una vez que se personaliza un controlador, afecta a todos los controles de ese tipo, en todas partes de la aplicación.

Cada clase de controlador expone la vista nativa para el control multiplataforma a través de su propiedad PlatformView. Se puede tener acceso a esta propiedad para establecer propiedades de vista nativas, invocar métodos de vista nativos y suscribirse a eventos de vista nativos. Además, el control multiplataforma implementado por el controlador se expone a través de su propiedad VirtualView.

Los controladores se pueden personalizar por plataforma mediante la compilación condicional, para el código de varios destinos en función de la plataforma. Como alternativa, puedes usar clases parciales para organizar el código en carpetas y archivos específicos de la plataforma. Para obtener más información sobre la compilación condicional, consulta Compilación condicional.

Personalización de controles

La vista MAUI de .NET Entry es un control de entrada de texto de una sola línea, que implementa la interfaz IEntry. EntryHandler asigna la vista Entry a las siguientes vistas nativas para cada plataforma:

  • Catalyst de iOS/Mac: UITextField
  • Android: AppCompatEditText
  • Windows: TextBox

En los diagramas siguientes se muestra cómo se asigna la vista Entry a sus vistas nativas a través de EntryHandler:

Entry handler architecture.

El asignador de propiedades Entry, en la clase EntryHandler, asigna las propiedades de control multiplataforma a la API de vista nativa. Esto garantiza que, cuando se establece una propiedad en Entry, la vista nativa subyacente se actualiza según sea necesario.

El asignador de propiedades se puede modificar para personalizar Entry en cada plataforma:

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPage : ContentPage
{
    public CustomizeEntryPage()
    {
        InitializeComponent();
        ModifyEntry();
    }

    void ModifyEntry()
    {
        Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
        {
#if ANDROID
            handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
            handler.PlatformView.EditingDidBegin += (s, e) =>
            {
                handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
            };
#elif WINDOWS
            handler.PlatformView.GotFocus += (s, e) =>
            {
                handler.PlatformView.SelectAll();
            };
#endif
        });
    }
}

En este ejemplo, la personalización Entry se produce en una clase de página. Por lo tanto, todos los controles Entry de Android, iOS y Windows se personalizarán una vez creada una instancia de CustomizeEntryPage. La personalización se realiza mediante el acceso a la propiedad handlers PlatformView, que proporciona acceso a la vista nativa que se asigna al control multiplataforma en cada plataforma. Después, el código nativo personaliza el controlador seleccionando todo el texto en Entry cuando obtiene el foco.

Para obtener más información acerca de los dominios, consulta Asignadores.

Personalización de una instancia de control específica

Los controladores son globales y la personalización de un controlador para un control dará como resultado que todos los controles del mismo tipo se personalicen en la aplicación. Sin embargo, los controladores para instancias de control específicas se pueden personalizar mediante las subclases del control y, luego, modificando el controlador para el tipo de control base solo cuando el control es del tipo subclase. Por ejemplo, para personalizar un control específico Entry en una página que contiene varios controles Entry, primero debe definir una subclase en el control Entry:

namespace CustomizeHandlersDemo.Controls
{
    internal class MyEntry : Entry
    {
    }
}

Después, puedes personalizar EntryHandler, a través de su asignador de propiedades, para realizar la modificación deseada solo en instancias MyEntry:

Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("MyCustomization", (handler, view) =>
{
    if (view is MyEntry)
    {
#if ANDROID
        handler.PlatformView.SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        handler.PlatformView.EditingDidBegin += (s, e) =>
        {
            handler.PlatformView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
        };
#elif WINDOWS
        handler.PlatformView.GotFocus += (s, e) =>
        {
            handler.PlatformView.SelectAll();
        };
#endif
    }
});

Si la personalización del controlador se realiza en la clase App, las instancias MyEntry de la aplicación se personalizarán según la modificación del controlador.

Personalización de un control mediante el ciclo de vida del controlador

Todos los controles de .NET MAUI basados en controladores admiten HandlerChanging y eventos HandlerChanged. El evento HandlerChanged se genera cuando la vista nativa que implementa el control multiplataforma está disponible e inicializado. El evento HandlerChanging se genera cuando el controlador del control está a punto de quitarse del control multiplataforma. Para obtener más información sobre los eventos del ciclo de vida del controlador, consulta Ciclo de vida del controlador.

El ciclo de vida del controlador se puede usar para realizar la personalización del controlador. Por ejemplo, para suscribirse y cancelar la suscripción a eventos de vista nativa, debe registrar controladores de eventos para los eventos HandlerChanged y HandlerChanging en el control multiplataforma que se está personalizando:

<Entry HandlerChanged="OnEntryHandlerChanged"
       HandlerChanging="OnEntryHandlerChanging" />

Los controladores se pueden personalizar por plataforma mediante la compilación condicional o mediante clases parciales para organizar el código en carpetas y archivos específicos de la plataforma. Cada enfoque se analizará a su vez mediante la personalización de Entry para que se seleccione todo su texto cuando obtenga el foco.

Compilación condicional

El archivo de código subyacente que contiene los controladores de eventos para los eventos HandlerChanged y HandlerChanging se muestra en el ejemplo siguiente, que usa la compilación condicional:

#if ANDROID
using AndroidX.AppCompat.Widget;
#elif IOS || MACCATALYST
using UIKit;
#elif WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
#endif

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryHandlerLifecyclePage : ContentPage
{
    public CustomizeEntryHandlerLifecyclePage()
    {
        InitializeComponent();
    }

    void OnEntryHandlerChanged(object sender, EventArgs e)
    {
        Entry entry = sender as Entry;
#if ANDROID
        (entry.Handler.PlatformView as AppCompatEditText).SetSelectAllOnFocus(true);
#elif IOS || MACCATALYST
        (entry.Handler.PlatformView as UITextField).EditingDidBegin += OnEditingDidBegin;
#elif WINDOWS
        (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
#endif
    }

    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e)
    {
        if (e.OldHandler != null)
        {
#if IOS || MACCATALYST
            (e.OldHandler.PlatformView as UITextField).EditingDidBegin -= OnEditingDidBegin;
#elif WINDOWS
            (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
#endif
        }
    }

#if IOS || MACCATALYST                   
    void OnEditingDidBegin(object sender, EventArgs e)
    {
        var nativeView = sender as UITextField;
        nativeView.PerformSelector(new ObjCRuntime.Selector("selectAll"), null, 0.0f);
    }
#elif WINDOWS
    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        var nativeView = sender as TextBox;
        nativeView.SelectAll();
    }
#endif
}

El evento HandlerChanged se genera después de que se haya creado e inicializado la vista nativa que implementa el control multiplataforma. Por lo tanto, su controlador de eventos es donde se deben realizar suscripciones de eventos nativas. Esto requiere convertir la propiedad PlatformView del controlador en el tipo, o tipo base, de la vista nativa para que se pueda tener acceso a eventos nativos. En este ejemplo, en iOS, Mac Catalyst y Windows, el evento OnEntryHandlerChanged se suscribe a eventos de vista nativos que se generan cuando las vistas nativas que implementan el Entry obtienen el foco.

Los controladores de eventos OnEditingDidBegin y OnGotFocus acceden a la vista nativa de Entry en sus respectivas plataformas y seleccionan todo el texto que se encuentra en Entry.

El evento HandlerChanging se genera antes de que se quite el controlador existente del control multiplataforma y antes de crear el nuevo controlador para el control multiplataforma. Por lo tanto, su controlador de eventos es donde se deben quitar las suscripciones de eventos nativos y se debe realizar otra limpieza. El objeto HandlerChangingEventArgs que acompaña a este evento tiene propiedades OldHandler y NewHandler, que se establecerán en los controladores antiguos y nuevos, respectivamente. En este ejemplo, el evento OnEntryHandlerChanging quita la suscripción a los eventos de vista nativa en iOS, Mac Catalyst y Windows.

Clases parciales

En lugar de usar la compilación condicional, también es posible usar clases parciales para organizar el código de personalización del control en carpetas y archivos específicos de la plataforma. Con este enfoque, el código de personalización se separa en una clase parcial multiplataforma y una clase parcial específica de la plataforma. En el ejemplo siguiente se muestra la clase parcial multiplataforma:

namespace CustomizeHandlersDemo.Views;

public partial class CustomizeEntryPartialMethodsPage : ContentPage
{
    public CustomizeEntryPartialMethodsPage()
    {
        InitializeComponent();
    }

    partial void ChangedHandler(object sender, EventArgs e);
    partial void ChangingHandler(object sender, HandlerChangingEventArgs e);

    void OnEntryHandlerChanged(object sender, EventArgs e) => ChangedHandler(sender, e);
    void OnEntryHandlerChanging(object sender, HandlerChangingEventArgs e) => ChangingHandler(sender, e);
}

Importante

La clase parcial multiplataforma no debe colocarse en ninguna de las carpetas secundarias Plataformas del proyecto.

En este ejemplo, los dos controladores de eventos llaman a métodos parciales denominados ChangedHandler y ChangingHandler, cuyas firmas se definen en la clase parcial multiplataforma. A continuación, las implementaciones de método parcial se definen en las clases parciales específicas de la plataforma, que deben colocarse en las carpetas secundarias Plataformas correctas para asegurarse de que el sistema de compilación solo intenta compilar código nativo al compilar para la plataforma específica. Por ejemplo, el código siguiente muestra la clase CustomizeEntryPartialMethodsPage en la carpeta Plataformas>Windows del proyecto:

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace CustomizeHandlersDemo.Views
{
    public partial class CustomizeEntryPartialMethodsPage : ContentPage
    {
        partial void ChangedHandler(object sender, EventArgs e)
        {
            Entry entry = sender as Entry;
            (entry.Handler.PlatformView as TextBox).GotFocus += OnGotFocus;
        }

        partial void ChangingHandler(object sender, HandlerChangingEventArgs e)
        {
            if (e.OldHandler != null)
            {
                (e.OldHandler.PlatformView as TextBox).GotFocus -= OnGotFocus;
            }
        }

        void OnGotFocus(object sender, RoutedEventArgs e)
        {
            var nativeView = sender as TextBox;
            nativeView.SelectAll();
        }
    }
}

La ventaja de este enfoque es que la compilación condicional no es necesaria y que los métodos parciales no tienen que implementarse en cada plataforma. Si no se proporciona la implementación, el método y todas las llamadas al método se quitan en tiempo de compilación. Para obtener información sobre los métodos parciales, consulta Métodos parciales.

Para obtener información sobre la organización de la carpeta Plataformas en un proyecto MAUI de .NET, consulta Clases y métodos parciales. Para obtener información sobre cómo configurar varios destinos para que no tengas que colocar código de plataforma en subcarpetas de la carpeta Plataformas, consulta Configuración de varios destinos.