Partilhar via


Personalizar controles com manipuladores

Visualizar exemplo. Visualizar o exemplo

Os manipuladores podem ser personalizados para aumentar a aparência e o comportamento de um controle entre plataformas 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 de plataforma cruzada, é 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 .NET MAUI tenham sido aplicados.
  • ModifyMapping, que modifica um mapeamento existente.
  • AppendToMapping, que modifica o mapeador para um manipulador após a aplicação dos mapeamentos de controle .NET MAUI.

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-chave usados pelos mapeamentos de controle .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 argumento handler que fornece uma instância do manipulador a ser personalizado.
    • Um view argumento que fornece uma instância do controlo multiplataforma que o manipulador implementa.

Importante

As personalizações do manipulador são globais e não têm escopo para uma instância de controle específica. A personalização do manipulador pode 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 a visualização nativa para o controlo de plataforma cruzada através da 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 nativa. Além disso, o controle multiplataforma implementado pelo manipulador é exposto por meio de sua propriedade VirtualView.

Os manipuladores podem ser personalizados por plataforma usando compilação condicional, de forma a adaptar o código para 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

A visualização .NET MAUI Entry é um controlo de entrada de texto em linha única que implementa a interface IEntry. O EntryHandler mapeia a vista Entry para as vistas nativas seguintes 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 vista Entry é mapeada para as suas vistas nativas através do EntryHandler:

Arquitetura do manipulador de entrada.

Arquitetura do manipulador de entrada.

O Entry mapeador de propriedades, na classe EntryHandler, mapeia as propriedades de controle entre plataformas para a API de exibição nativa. Isso garante que, quando uma propriedade é definida em um Entry, a exibiçã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 do CustomizeEntryPage for criada. A personalização é realizada ao aceder à propriedade handlers PlatformView, que proporciona acesso à vista nativa que mapeia para o controlo multiplataforma em cada plataforma. Em seguida, o código nativo personaliza o manipulador selecionando todo o texto em 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 personalizar um manipulador para um controle resultará em todos os controles do mesmo tipo sendo personalizados em seu aplicativo. No entanto, manipuladores para instâncias de controle específicas podem ser personalizados subclassificando o controle e, em seguida, modificando o manipulador para o tipo de controle base somente quando o controle é do tipo subclassificado. Por exemplo, para personalizar um controle específico Entry em uma página que contém vários Entry controles, você deve primeiro subclassificar o Entry controle:

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

Em seguida, pode personalizar o EntryHandler por meio do seu mapeador de propriedades, para executar a modificação desejada apenas às 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 as MyEntry instâncias no 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 .NET MAUI baseados em handlers suportam eventos HandlerChanging e HandlerChanged. O HandlerChanged evento é gerado quando a exibição nativa que implementa o controle de plataforma cruzada está disponível e inicializada. O HandlerChanging evento é gerado quando o manipulador do controle está prestes a ser removido do controle de plataforma cruzada. Para obter mais informações sobre eventos do 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 subscrição de eventos de visualização nativos, deves registar manipuladores de eventos para os eventos HandlerChanged e HandlerChanging no controlo de plataforma cruzada que está a ser personalizado.

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

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

Compilação condicional

O arquivo code-behind que contém os manipuladores de eventos para os HandlerChanged eventos e HandlerChanging é mostrado no exemplo a seguir, que usa 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 depois que a exibição nativa que implementa o controle de plataforma cruzada foi criada e inicializada. Portanto, seu manipulador de eventos é onde as assinaturas de eventos nativos devem ser executadas. Isso requer a conversão da propriedade do manipulador PlatformView para o tipo, ou tipo base, da exibição nativa para que os eventos nativos possam ser acessados. Neste exemplo, no iOS, Mac Catalyst e Windows, o evento OnEntryHandlerChanged inscreve-se em eventos de vista nativos que são gerados quando as vistas nativas que implementam o Entry ganham foco.

Os manipuladores de eventos OnEditingDidBegin e OnGotFocus acedem à vista nativa para o Entry nas suas respetivas plataformas e selecionam todo o texto que está no Entry.

O HandlerChanging evento é gerado antes que o manipulador existente seja removido do controle de plataforma cruzada e antes que o novo manipulador para o controle de plataforma cruzada 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 para os manipuladores antigo e novo, respectivamente. Neste exemplo, o evento OnEntryHandlerChanging remove a subscrição dos eventos de visualização nativa no iOS, Mac Catalyst e Windows.

Aulas parciais

Em vez de usar a compilação condicional, também é possível usar classes parciais para organizar seu 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 entre plataformas e uma classe parcial específica da plataforma:

  • A classe parcial entre plataformas 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 filhas de Plataformas do seu projeto, porque isso a tornaria uma classe específica da plataforma.
  • A classe parcial específica da plataforma normalmente implementa os membros definidos na classe parcial entre plataformas e é criada para uma única plataforma. Essa classe deve ser colocada na pasta filho da pasta Plataformas para a 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 entre plataformas. As implementações de método parcial 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 apenas tente criar código nativo ao criar para a plataforma específica. Por exemplo, o código a seguir mostra a CustomizeEntryPartialMethodsPage classe na pasta Platforms>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 removidas em tempo de compilação. Para obter informações sobre métodos parciais, consulte Métodos parciais.

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