Partilhar via


Personalizar controles com manipuladores

Procurar amostra. Procurar no exemplo

Os manipuladores podem ser personalizados para aprimorar a aparência e o comportamento de um controle multiplataforma além da personalização possível por meio da API do controle. Essa personalização, que modifica as exibições nativas para o controle multiplataforma, é obtida modificando o mapeador para um manipulador com um dos seguintes métodos:

  • PrependToMapping, que modifica o mapeador de um manipulador antes que os mapeamentos de controle .NET MAUI tenham sido aplicados.
  • ModifyMapping, que modifica um mapeamento existente.
  • AppendToMapping, que modifica o mapeador para um manipulador depois que os mapeamentos de controle do .NET MAUI foram aplicados.

Cada um desses métodos tem uma assinatura idêntica que requer dois argumentos:

  • Uma chave baseada em string. Ao modificar um dos mapeamentos fornecidos pelo .NET MAUI, a chave usada pelo .NET MAUI deve ser especificada. Os valores de chave usados pelos mapeamentos de controle do .NET MAUI são baseados em nomes de interface e propriedade, por exemplo nameof(IEntry.IsPassword). As interfaces e suas propriedades que abstraem cada controle multiplataforma podem ser encontradas aqui. Esse é o formato de chave que deve ser usado se você quiser que a personalização do manipulador seja executada sempre que uma propriedade for alterada. Caso contrário, a chave pode ser um valor arbitrário que não precisa corresponder ao nome de uma propriedade exposta por um tipo. Por exemplo, MyCustomization pode ser especificado como uma chave, com qualquer modificação de exibição nativa sendo executada como a personalização. No entanto, uma consequência desse formato de chave é que a personalização do manipulador só será executada quando o mapeador do manipulador for modificado pela primeira vez.
  • Um Action que representa o método que executa a personalização do manipulador. O Action especifica dois argumentos:
    • Um argumento handler que fornece uma instância do manipulador que está sendo personalizado.
    • Um argumento view que fornece uma instância do controle multiplataforma que o manipulador implementa.

Importante

As personalizações de manipulador são globais e não têm escopo para uma instância de controle específica. A personalização do manipulador tem permissão para acontecer em qualquer lugar do seu aplicativo. Depois que um manipulador é personalizado, ele afeta todos os controles desse tipo, em todos os lugares do seu aplicativo.

Cada classe de manipulador expõe o modo de exibição nativo para o controle multiplataforma por meio de sua propriedade PlatformView. Essa propriedade pode ser acessada para definir propriedades de exibição nativas, invocar métodos de exibição nativos e assinar eventos de exibição nativos. Além disso, o controle multiplataforma implementado pelo manipulador é exposto por meio de sua propriedade VirtualView.

Os manipuladores podem ser personalizados por plataforma usando a compilação condicional, para código de vários destinos com base na plataforma. Como alternativa, você pode usar classes parciais para organizar seu código em pastas e arquivos específicos da plataforma. Para obter mais informações sobre compilação condicional, consulte Compilação condicional.

Personalizar um controle

O modo de exibição Entry .NET MAUI é um controle de entrada de texto de linha única que implementa a interface IEntry. O EntryHandler mapeia a exibição Entry para as seguintes exibições nativas para cada plataforma:

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

Os diagramas a seguir mostram como a exibição Entry é mapeada para suas exibições nativas por meio do EntryHandler:

Arquitetura do manipulador de entrada.

O mapeador de propriedades Entry, na classe EntryHandler, mapeia as propriedades de controle multiplataforma para a API de exibição nativa. Isso garante que, quando uma propriedade estiver definida em um modo de exibição Entrynativo subjacente, seja atualizada conforme necessário.

O mapeador de propriedades pode ser modificado para personalizar Entry em 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
        });
    }
}

Neste exemplo, a personalização Entry ocorre em uma classe de página. Portanto, todos os controles Entry no Android, iOS e Windows serão personalizados assim que uma instância do CustomizeEntryPage for criada. A personalização é executada acessando a propriedade PlatformView de manipuladores, que fornece acesso à exibição nativa que é mapeada para o controle multiplataforma em cada plataforma. Em seguida, o código nativo personaliza o manipulador selecionando todo o texto no Entry quando ele ganha foco.

Para obter mais informações sobre mapeadores, consulte Mapeadores.

Personalizar uma instância de controle específica

Os manipuladores são globais e a personalização de um manipulador para um controle resultará em todos os controles do mesmo tipo sendo personalizados em seu aplicativo. No entanto, os manipuladores para instâncias de controle específicas podem ser personalizados subclasse o controle e, em seguida, modificando o manipulador para o tipo de controle base somente quando o controle é do tipo subclasse. Por exemplo, para personalizar um controle Entry específico em uma página que contenha vários controles Entry, primeiro você deve subclassificar o controle Entry:

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

Em seguida, você pode personalizar o EntryHandler, por meio de seu mapeador de propriedades, para executar a modificação desejada somente para instâncias 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
    }
});

Se a personalização do manipulador for executada em sua classe App, todas as instâncias MyEntry do aplicativo serão personalizadas de acordo com a modificação do manipulador.

Personalizar um controle usando o ciclo de vida do manipulador

Todos os controles do .NET MAUI baseados em manipuladores dão suporte a eventos HandlerChanging e HandlerChanged. O evento HandlerChanged é gerado quando a exibição nativa que implementa o controle multiplataforma está disponível e inicializada. O evento HandlerChanging é gerado quando o manipulador do controle está prestes a ser removido do controle multiplataforma. Para obter mais informações sobre eventos de ciclo de vida do manipulador, consulte Ciclo de vida do manipulador.

O ciclo de vida do manipulador pode ser usado para executar a personalização do manipulador. Por exemplo, para assinar e cancelar a assinatura de eventos de exibição nativa, você deve registrar manipuladores de eventos para os eventos HandlerChanged e HandlerChanging no controle multiplataforma que está sendo personalizado:

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

Os manipuladores podem ser personalizados por plataforma usando a compilação condicional ou usando classes parciais para organizar seu código em pastas e arquivos específicos da plataforma. Cada abordagem será discutida, por sua vez, personalizando um Entry para que todo o texto seja selecionado quando ele ganhar foco.

Compilação condicional

O arquivo code-behind que contém os manipuladores de eventos para os eventos HandlerChanged e HandlerChanging é mostrado no exemplo a seguir, que usa a compilação 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
}

O evento HandlerChanged é gerado após a exibição nativa que implementa o controle multiplataforma ter sido criada e inicializada. Portanto, seu manipulador de eventos é onde as assinaturas de eventos nativos devem ser executadas. Isso requer a conversão da propriedade PlatformView do manipulador para o tipo, ou tipo base, do modo de exibição nativo para que os eventos nativos possam ser acessados. Neste exemplo, no iOS, Mac Catalyst e Windows, o evento OnEntryHandlerChanged assina eventos de exibição nativos gerados quando os modos de exibição nativos que implementam o foco de Entry ganho.

Os manipuladores de eventos OnEditingDidBegin e OnGotFocus acessam o modo de exibição nativo para o Entry em suas respectivas plataformas e selecionam todo o texto que está no Entry.

O evento HandlerChanging é gerado antes que o manipulador existente seja removido do controle multiplataforma e antes que o novo manipulador do controle multiplataforma seja criado. Portanto, seu manipulador de eventos é onde as assinaturas de eventos nativos devem ser removidas e outras limpezas devem ser executadas. O objeto HandlerChangingEventArgs que acompanha esse evento tem as propriedades OldHandler e NewHandler, que serão definidas como os manipuladores antigos e novos, respectivamente. Neste exemplo, o evento OnEntryHandlerChanging remove a assinatura para os eventos de exibição nativa no iOS, Mac Catalyst e Windows.

Classes parciais

Em vez de usar a compilação condicional, também é possível usar classes parciais para organizar o código de personalização de controle em pastas e arquivos específicos da plataforma. Com essa abordagem, seu código de personalização é separado em uma classe parcial multiplataforma e uma classe parcial específica da plataforma:

  • A classe parcial multiplataforma normalmente define membros, mas não os implementa, e é criada para todas as plataformas. Essa classe não deve ser colocada em nenhuma das pastas filho Plataformas do seu projeto, pois isso a tornaria uma classe específica de plataforma.
  • A classe parcial específica de plataforma normalmente implementa os membros definidos na classe parcial multiplataforma e é criada para uma única plataforma. Essa classe deve ser colocada na pasta filha da pasta Plataformas da plataforma escolhida.

O exemplo a seguir mostra uma classe 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);
}

Neste exemplo, os dois manipuladores de eventos chamam métodos parciais nomeados ChangedHandler e ChangingHandler, cujas assinaturas são definidas na classe parcial de plataforma cruzada. As implementações de método parcial são definidas nas classes parciais específicas da plataforma, que devem ser colocadas nas pastas filho das Plataformas corretas para garantir que o sistema de build tente criar apenas código nativo ao criar para a plataforma específica. Por exemplo, o código a seguir mostra a classe CustomizeEntryPartialMethodsPage na pasta Plataformas>Windows do projeto:

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();
        }
    }
}

A vantagem dessa abordagem é que a compilação condicional não é necessária e que os métodos parciais não precisam ser implementados em cada plataforma. Se uma implementação não for fornecida em uma plataforma, o método e todas as chamadas para o método serão removidos no momento da compilação. Para obter informações sobre métodos parciais, consulte Métodos parciais.

Para obter informações sobre a organização da pasta Plataformas em um projeto .NET MAUI, consulte Classes e métodos parciais. Para obter informações sobre como configurar vários direcionamentos para que você não precise colocar o código da plataforma em subpastas da pasta Plataformas, consulte Configurar multiplataforma.