Compartir a través de


Personaliza controles con manejadores

Examinar ejemplo. Examinar el ejemplo

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 mapeador para un controlador con uno de los métodos siguientes.

  • PrependToMapping, que modifica el mapeador para 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 mapeador de un controlador tras haber aplicado las asignaciones de controles de .NET MAUI.

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

  • Clave basada en string. Al modificar uno de los mapeos proporcionados por .NET MAUI, se debe especificar la clave utilizada 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). Puede encontrar las interfaces que abstraen cada control multiplataforma en el repositorio dotnet/maui. 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 al nombre de una propiedad expuesta por un tipo. Por ejemplo, MyCustomization se puede especificar como clave, y cualquier modificación de vista nativa se realizará como parte de la personalización. Sin embargo, una consecuencia de este formato de clave es que la personalización del controlador solo se ejecutará la primera vez que se modifique el mapeador del controlador.
  • Un Action que representa el método que realiza la personalización del manejador. 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 del control multiplataforma mediante 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 para cada plataforma utilizando compilación condicional, para orientar el código a múltiples plataformas en función de la plataforma. Como alternativa, puede 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, consulte Compilación condicional.

Personalizar un control

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

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

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

Arquitectura del controlador de entrada.

Arquitectura del controlador de entrada.

El mapeador de propiedades Entry, en la clase EntryHandler, mapea 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 actualice según sea necesario.

El mapeador 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 Entry personalización se produce en una clase de página. Por lo tanto, todos los Entry controles 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. A continuación, el código nativo personaliza el controlador seleccionando todo el texto en el Entry cuando obtiene el foco.

Para obtener más información sobre los mapeadores, vea Mappers.

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 la creación de subclases del control y luego modificando el controlador para el tipo de control base solo cuando el control es del tipo de subclase. Por ejemplo, para personalizar un control específico Entry en una página que incluye varios controles Entry, primero debe subclasificar el control Entry.

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

A continuación, puede 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 realizada al controlador.

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

Todos los controles de .NET MAUI basados en controladores admiten eventos HandlerChanging y HandlerChanged. El HandlerChanged evento se genera cuando la vista nativa que implementa el control multiplataforma está disponible e inicializado. El evento HandlerChanging se genera cuando el manejador del control está por ser retirado del control multiplataforma. Para obtener más información sobre los eventos del ciclo de vida del controlador, consulte 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 darse de baja de eventos de vista nativa, debe registrar controladores de eventos para los eventos HandlerChanged y HandlerChanging en el control multiplataforma que está siendo personalizado.

<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 un Entry para que se seleccione todo su texto cuando reciba el foco.

Compilación condicional

El archivo de código subyacente que contiene los controladores de eventos para los HandlerChanged eventos 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
}
#if ANDROID
using Microsoft.Maui.Platform;
#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 MauiAppCompatEditText).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 HandlerChanged evento se genera después de que se haya creado e inicializado la vista nativa que implementa el control multiplataforma. Por lo tanto, su manejador de eventos es donde deben realizarse las suscripciones nativas a eventos. Esto requiere convertir la PlatformView propiedad 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 los eventos de vista nativos que se generan cuando las vistas nativas que implementan el Entry ganan el foco.

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

El HandlerChanging evento 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 manejador de eventos es donde se deben quitar las suscripciones de eventos nativos y se deben realizar otras tareas de limpieza. El HandlerChangingEventArgs objeto que acompaña a este evento tiene las propiedades OldHandler y NewHandler, las cuales se establecerán en los manejadores antiguos y nuevos, respectivamente. En este ejemplo, el OnEntryHandlerChanging evento 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:

  • Normalmente, la clase parcial multiplataforma define miembros, pero no los implementa y se compila para todas las plataformas. Esta clase no debe colocarse en ninguna de las carpetas secundarias Plataformas del proyecto, ya que hacerlo haría que sea una clase específica de la plataforma.
  • Normalmente, la clase parcial específica de la plataforma implementa los miembros definidos en la clase parcial multiplataforma y se compila para una sola plataforma. Esta clase debe colocarse en la carpeta secundaria de la carpeta Plataformas de la plataforma elegida.

En el ejemplo siguiente se muestra una 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);
}

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 CustomizeEntryPartialMethodsPage clase 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 una implementación en una plataforma, 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, vea Métodos parciales.

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