Compartilhar via


Personalizar controles com manipuladores

Navegar na amostra. Navegar na amostra

Os manipuladores podem ser personalizados para aumentar 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 para um manipulador antes que os mapeamentos de controle do .NET MAUI tenham sido aplicados.
  • ModifyMapping, que modifica um mapeamento existente.
  • AppendToMapping, que modifica o mapeador de 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:

  • Chave baseada em string. Ao modificar um dos mapeamentos fornecidos pelo .NET MAUI, a chave usada pelo MAUI do .NET 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). Você pode encontrar as interfaces que abstraem cada controle multiplataforma no repositório dotnet/maui. 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 handler argumento que fornece uma instância do manipulador que está sendo personalizado.
    • Um view argumento 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 pode ocorrer 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 a visualização nativa para o controle multiplataforma por meio da 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 para cada plataforma usando compilação condicional, permitindo o direcionamento de código 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 a compilação condicional.

Personalizar um controle

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

  • iOS/Mac Catalyst: UITextField
  • Android: AppCompatEditText
  • Windows: TextBox
  • iOS/Mac Catalyst: UITextField
  • Android: MauiAppCompatEditText
  • 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.

Arquitetura do manipulador de entrada.

O Entry mapeador de propriedades, na EntryHandler classe, mapeia as propriedades de controle multiplataforma para a API de exibição nativa. Isso garante que, quando uma propriedade é definida em um Entry, a visualização nativa 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 Entry personalização ocorre em uma classe de página. Portanto, todos os Entry controles no Android, iOS e Windows serão personalizados assim que uma instância de CustomizeEntryPage for criada. A personalização é realizada acessando a propriedade PlatformView dos manipuladores, que proporciona acesso à visualização nativa mapeada para o controle multiplataforma em cada plataforma. Em seguida, o código nativo personaliza o manipulador ao selecionar todo o texto em Entry quando este ganha foco.

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

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 específicas de controle podem ser personalizados subclassificando o controle e, para então, modificar o manipulador para o tipo de controle base somente quando o controle for do tipo subclasseado. Por exemplo, para personalizar um controle específico Entry em uma página que contenha vários Entry controles, você deve primeiro subclassificar o Entry controle.

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

Você pode, então, personalizar o EntryHandler, por meio de seu mapeador de propriedades, para executar a modificação desejada somente em instâncias de 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 App classe, todas MyEntry as instâncias 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 baseados em manipuladores do .NET MAUI dão suporte a eventos HandlerChanging e HandlerChanged. O HandlerChanged evento é gerado quando a exibição nativa que implementa o controle multiplataforma está disponível e inicializada. O HandlerChanging evento é 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 o 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 de code-behind que contém os manipuladores 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
}
#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
}

O HandlerChanged evento é 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 convertendo a propriedade PlatformView do manipulador para o tipo, ou tipo base, da visualização nativa para que os eventos nativos possam ser acessados. Neste exemplo, no iOS, Mac Catalyst e Windows, o evento OnEntryHandlerChanged se inscreve nos eventos das visualizações nativas que são disparados quando as visualizações nativas que implementam o Entry ganham foco.

Os manipuladores de eventos OnEditingDidBegin e OnGotFocus acessam a visualização nativa para as plataformas respectivas do Entry e selecionam todo o texto que está no Entry.

O HandlerChanging evento é 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 HandlerChangingEventArgs objeto que acompanha esse evento tem OldHandler e NewHandler propriedades, que serão definidas como os manipuladores antigos e novos, respectivamente. Neste exemplo, o evento OnEntryHandlerChanging remove a assinatura dos eventos de visualizaçã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 em uma classe parcial específica da plataforma:

  • A classe parcial multiplataforma normalmente define os membros, mas não os implementa e é criado para todas as plataformas. Essa classe não deve ser colocada em nenhuma das subpastas de Plataformas do seu projeto, pois isso a tornaria uma classe específica de uma plataforma.
  • A classe parcial específica da plataforma normalmente implementa os membros definidos na classe parcial multiplataforma e é criada para uma única plataforma. Essa classe deve ser colocada na pasta filho 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étodos parciais são então definidas nas classes parciais específicas da plataforma, que devem ser colocadas nas pastas filhas corretas de Plataformas para garantir que o sistema de compilação tente criar apenas código nativo ao compilar para a plataforma específica. Por exemplo, o código a seguir mostra a CustomizeEntryPartialMethodsPage classe 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 multi-direcionamento.