Condividi tramite


Eseguire la migrazione di un renderer personalizzato Xamarin.Forms a un gestore MAUI .NET

In Xamarin.Forms è possibile usare renderer personalizzati per personalizzare l'aspetto e il comportamento di un controllo e creare nuovi controlli multipiattaforma. Ogni renderer personalizzato ha un riferimento al controllo multipiattaforma e spesso si basa su INotifyPropertyChanged per inviare notifiche di modifica delle proprietà. Invece di usare renderer personalizzati, .NET Multipiattaforma App UI (.NET MAUI) introduce un nuovo concetto denominato gestore.

I gestori offrono molti miglioramenti delle prestazioni rispetto ai renderer personalizzati. In Xamarin.Forms la ViewRenderer classe crea un elemento padre. Ad esempio, in Android viene creato un ViewGroup oggetto che viene usato per le attività di posizionamento ausiliarie. In .NET MAUI la ViewHandler<TVirtualView,TPlatformView> classe non crea un elemento padre, che consente di ridurre le dimensioni della gerarchia visiva e migliorare le prestazioni dell'app. I gestori separano anche i controlli della piattaforma dal framework. Il controllo della piattaforma deve gestire solo le esigenze del framework. Questo non è solo più efficiente, ma è molto più facile estendere o eseguire l'override quando necessario. I gestori sono adatti anche per il riutilizzo da parte di altri framework, ad esempio Comet e Fabulous. Per altre informazioni sui gestori, vedere Gestori.

In Xamarin.Forms il OnElementChanged metodo in un renderer personalizzato crea il controllo della piattaforma, inizializza i valori predefiniti, sottoscrive gli eventi e gestisce l'elemento Xamarin.Forms aOldElement cui è stato associato il renderer e l'elemento a cui è associato il renderer (NewElement). Inoltre, un singolo OnElementPropertyChanged metodo definisce le operazioni da richiamare quando si verifica una modifica di proprietà nel controllo multipiattaforma. .NET MAUI semplifica questo approccio, in modo che ogni modifica di proprietà venga gestita da un metodo separato e in modo che il codice per creare il controllo della piattaforma, eseguire la configurazione del controllo ed eseguire la pulizia del controllo sia separato in metodi distinti.

Il processo per la migrazione di un controllo personalizzato Xamarin.Forms supportato da renderer personalizzati in ogni piattaforma a un controllo personalizzato MAUI .NET supportato da un gestore in ogni piattaforma è il seguente:

  1. Creare una classe per il controllo multipiattaforma, che fornisce l'API pubblica del controllo. Per altre informazioni, vedere Creare il controllo multipiattaforma.
  2. Creare una partial classe del gestore. Per altre informazioni, vedere Creare il gestore.
  3. Nella classe del gestore creare un PropertyMapper dizionario, che definisce le azioni da eseguire quando si verificano modifiche alle proprietà multipiattaforma. Per altre informazioni, vedere Creare il mapper di proprietà.
  4. Creare partial classi del gestore per ogni piattaforma che creano le visualizzazioni native che implementano il controllo multipiattaforma. Per altre informazioni, vedere Creare i controlli della piattaforma.
  5. Registrare il gestore usando i ConfigureMauiHandlers metodi e AddHandler nella classe dell'app MauiProgram . Per altre informazioni, vedere Registrare il gestore.

Il controllo multipiattaforma può quindi essere utilizzato. Per altre informazioni, vedere Utilizzare il controllo multipiattaforma.

In alternativa, i renderer personalizzati che personalizzano i controlli Xamarin.Forms possono essere convertiti in modo che modifichino i gestori MAUI .NET. Per altre informazioni, vedere Personalizzare i controlli con i gestori.

Creare il controllo multipiattaforma

Per creare un controllo multipiattaforma, è necessario creare una classe che deriva da View:

namespace MyMauiControl.Controls
{
    public class CustomEntry : View
    {
        public static readonly BindableProperty TextProperty =
            BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomEntry), null);

        public static readonly BindableProperty TextColorProperty =
            BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(CustomEntry), null);

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public Color TextColor
        {
            get { return (Color)GetValue(TextColorProperty); }
            set { SetValue(TextColorProperty, value); }
        }
    }
}

Il controllo deve fornire un'API pubblica a cui sarà possibile accedere dal gestore e controllare i consumer. I controlli multipiattaforma devono derivare da View, che rappresenta un elemento visivo usato per posizionare layout e visualizzazioni sullo schermo.

Creare il gestore

Dopo aver creato il controllo multipiattaforma, è necessario creare una partial classe per il gestore:

#if IOS || MACCATALYST
using PlatformView = Microsoft.Maui.Platform.MauiTextField;
#elif ANDROID
using PlatformView = AndroidX.AppCompat.Widget.AppCompatEditText;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.Controls.TextBox;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
using MyMauiControl.Controls;
using Microsoft.Maui.Handlers;

namespace MyMauiControl.Handlers
{
    public partial class CustomEntryHandler
    {
    }
}

La classe del gestore è una classe parziale la cui implementazione verrà completata in ogni piattaforma con una classe parziale aggiuntiva.

Le istruzioni condizionali using definiscono il PlatformView tipo in ogni piattaforma. L'istruzione condizionale using finale definisce PlatformView che deve essere uguale a System.Object. Questa operazione è necessaria in modo che il PlatformView tipo possa essere usato all'interno del gestore per l'utilizzo in tutte le piattaforme. L'alternativa consiste nel definire la PlatformView proprietà una volta per ogni piattaforma, usando la compilazione condizionale.

Creare il mapper di proprietà

Ogni gestore fornisce in genere un mapper di proprietà, che definisce le azioni da eseguire quando si verifica una modifica della proprietà nel controllo multipiattaforma. Il PropertyMapper tipo è un oggetto Dictionary che esegue il mapping delle proprietà del controllo multipiattaforma alle azioni associate.

Nota

Il mapper di proprietà è la sostituzione del OnElementPropertyChanged metodo in un renderer personalizzato Xamarin.Forms.

PropertyMapper è definito nella classe MAUI di ViewHandler<TVirtualView,TPlatformView> .NET e richiede due argomenti generici da fornire:

  • Classe per il controllo multipiattaforma, che deriva da View.
  • Classe per il gestore.

L'esempio di codice seguente illustra la CustomEntryHandler classe estesa con la PropertyMapper definizione :

public partial class CustomEntryHandler
{
    public static PropertyMapper<CustomEntry, CustomEntryHandler> PropertyMapper = new PropertyMapper<CustomEntry, CustomEntryHandler>(ViewHandler.ViewMapper)
    {
        [nameof(CustomEntry.Text)] = MapText,
        [nameof(CustomEntry.TextColor)] = MapTextColor
    };

    public CustomEntryHandler() : base(PropertyMapper)
    {
    }
}

è un oggetto PropertyMapper la cui chiave è un string oggetto e il cui valore è un oggetto genericoAction.Dictionary Rappresenta string il nome della proprietà del controllo multipiattaforma e Action rappresenta un static metodo che richiede il gestore e il controllo multipiattaforma come argomenti. Ad esempio, la firma del MapText metodo è public static void MapText(CustomEntryHandler handler, CustomEntry view).

Ogni gestore della piattaforma deve fornire implementazioni delle azioni, che modificano le API di visualizzazione nativa. In questo modo, quando una proprietà viene impostata su un controllo multipiattaforma, la visualizzazione nativa sottostante verrà aggiornata in base alle esigenze. Il vantaggio di questo approccio è che consente di personalizzare facilmente il controllo multipiattaforma, perché il mapper delle proprietà può essere modificato dai consumer di controlli multipiattaforma senza sottoclasse. Per altre informazioni, vedere Personalizzare i controlli con i gestori.

Creare i controlli della piattaforma

Dopo aver creato i mapper per il gestore, è necessario fornire implementazioni del gestore in tutte le piattaforme. A tale scopo, è possibile aggiungere implementazioni parziali del gestore classi nelle cartelle figlio della cartella Piattaforme . In alternativa, è possibile configurare il progetto per supportare multitargeting basato su nomi file o multitargeting basato su cartelle o entrambi.

Il multitargeting basato su nome file viene configurato aggiungendo il codice XML seguente al file di progetto, come elementi figlio del <Project> nodo:

<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-android')) != true">
  <Compile Remove="**\*.Android.cs" />
  <None Include="**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- iOS and Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true AND $(TargetFramework.StartsWith('net8.0-maccatalyst')) != true">
  <Compile Remove="**\*.MaciOS.cs" />
  <None Include="**\*.MaciOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
  <Compile Remove="**\*.Windows.cs" />
  <None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

Per altre informazioni sulla configurazione di più destinazioni, vedere Configurare multitargeting.

Ogni classe del gestore della piattaforma deve essere una classe parziale e derivare dalla ViewHandler<TVirtualView,TPlatformView> classe , che richiede due argomenti di tipo:

  • Classe per il controllo multipiattaforma, che deriva da View.
  • Tipo della visualizzazione nativa che implementa il controllo multipiattaforma sulla piattaforma. Deve essere identico al tipo della PlatformView proprietà nel gestore.

Importante

La ViewHandler<TVirtualView,TPlatformView> classe fornisce VirtualView proprietà e PlatformView . La VirtualView proprietà viene utilizzata per accedere al controllo multipiattaforma dal relativo gestore. La PlatformView proprietà viene usata per accedere alla visualizzazione nativa in ogni piattaforma che implementa il controllo multipiattaforma.

Ognuna delle implementazioni del gestore della piattaforma deve eseguire l'override dei metodi seguenti:

  • CreatePlatformView, che deve creare e restituire la visualizzazione nativa che implementa il controllo multipiattaforma.
  • ConnectHandler, che deve eseguire qualsiasi configurazione della visualizzazione nativa, ad esempio l'inizializzazione della visualizzazione nativa e l'esecuzione di sottoscrizioni di eventi.
  • DisconnectHandler, che deve eseguire qualsiasi pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti. Questo metodo non viene intenzionalmente richiamato da .NET MAUI. È invece necessario richiamarlo manualmente da una posizione appropriata nel ciclo di vita dell'app. Per altre informazioni, vedere Pulizia della visualizzazione nativa.
  • CreatePlatformView, che deve creare e restituire la visualizzazione nativa che implementa il controllo multipiattaforma.
  • ConnectHandler, che deve eseguire qualsiasi configurazione della visualizzazione nativa, ad esempio l'inizializzazione della visualizzazione nativa e l'esecuzione di sottoscrizioni di eventi.
  • DisconnectHandler, che deve eseguire qualsiasi pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti. Questo metodo viene richiamato automaticamente da .NET MAUI per impostazione predefinita, anche se questo comportamento può essere modificato. Per altre informazioni, vedere Disconnessione del gestore di controllo.

Nota

Le CreatePlatformViewsostituzioni , ConnectHandlere DisconnectHandler sono le sostituzioni per il OnElementChanged metodo in un renderer personalizzato di Xamarin.Forms.

Ogni gestore della piattaforma deve implementare anche le azioni definite nei dizionari del mapper. Inoltre, ogni gestore della piattaforma deve fornire codice, in base alle esigenze, per implementare la funzionalità del controllo multipiattaforma sulla piattaforma. In alternativa, per controlli più complessi è possibile specificare un tipo aggiuntivo.

L'esempio seguente illustra l'implementazione CustomEntryHandler in Android:

#nullable enable
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using MyMauiControl.Controls;

namespace MyMauiControl.Handlers
{
    public partial class CustomEntryHandler : ViewHandler<CustomEntry, AppCompatEditText>
    {
        protected override AppCompatEditText CreatePlatformView() => new AppCompatEditText(Context);

        protected override void ConnectHandler(AppCompatEditText platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(AppCompatEditText platformView)
        {
            // Perform any native view cleanup here
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }

        public static void MapText(CustomEntryHandler handler, CustomEntry view)
        {
            handler.PlatformView.Text = view.Text;
            handler.PlatformView?.SetSelection(handler.PlatformView?.Text?.Length ?? 0);
        }

        public static void MapTextColor(CustomEntryHandler handler, CustomEntry view)
        {
            handler.PlatformView?.SetTextColor(view.TextColor.ToPlatform());
        }
    }
}

CustomEntryHandler deriva dalla ViewHandler<TVirtualView,TPlatformView> classe , con l'argomento generico che specifica il tipo di controllo multipiattaforma CustomEntry e l'argomento AppCompatEditText che specifica il tipo di controllo nativo.

L'override CreatePlatformView crea e restituisce un AppCompatEditText oggetto . L'override ConnectHandler è il percorso in cui eseguire qualsiasi configurazione di visualizzazione nativa richiesta. L'override DisconnectHandler è il percorso in cui eseguire qualsiasi pulizia della visualizzazione nativa e quindi chiama il Dispose metodo nell'istanza AppCompatEditText di .

Il gestore implementa anche le azioni definite nel dizionario mapper proprietà. Ogni azione viene eseguita in risposta a una modifica di proprietà nel controllo multipiattaforma ed è un static metodo che richiede istanze di controllo multipiattaforma e del gestore come argomenti. In ogni caso, Action chiama i metodi definiti nel controllo nativo.

Registrare il gestore

Un controllo personalizzato e il relativo gestore devono essere registrati con un'app, prima che possa essere utilizzato. Questo dovrebbe verificarsi nel CreateMauiApp metodo nella MauiProgram classe nel progetto di app, che è il punto di ingresso multipiattaforma per l'app:

using Microsoft.Extensions.Logging;
using MyMauiControl.Controls;
using MyMauiControl.Handlers;

namespace MyMauiControl;

public static class MauiProgram
{
  public static MauiApp CreateMauiApp()
  {
    var builder = MauiApp.CreateBuilder();
    builder
      .UseMauiApp<App>()
      .ConfigureFonts(fonts =>
      {
        fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
      })
      .ConfigureMauiHandlers(handlers =>
      {
        handlers.AddHandler(typeof(CustomEntry), typeof(CustomEntryHandler));
      });

#if DEBUG
    builder.Logging.AddDebug();
#endif

    return builder.Build();
  }
}

Il gestore viene registrato con il ConfigureMauiHandlers metodo e AddHandler . Il primo argomento del AddHandler metodo è il tipo di controllo multipiattaforma, con il secondo argomento come tipo di gestore.

Nota

Questo approccio di registrazione evita l'analisi degli assembly di Xamarin.Forms, che è lenta e costosa.

Utilizzare il controllo multipiattaforma

Dopo aver registrato il gestore con l'app, è possibile usare il controllo multipiattaforma:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:MyMauiControl.Controls"
             x:Class="MyMauiControl.MainPage">
    <Grid>
        <controls:CustomEntry Text="Hello world"
                              TextColor="Blue" />
    </Grid>
</ContentPage>

Pulizia della visualizzazione nativa

L'implementazione del gestore di ogni piattaforma esegue l'override dell'implementazione DisconnectHandler , che viene usata per eseguire la pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti. Tuttavia, questa sostituzione non viene intenzionalmente richiamata da .NET MAUI. È invece necessario richiamarlo manualmente da una posizione appropriata nel ciclo di vita dell'app. Potrebbe trattarsi di quando la pagina contenente il controllo viene spostata lontano, causando la generazione dell'evento della Unloaded pagina.

Un gestore eventi per l'evento della Unloaded pagina può essere registrato in XAML:

<ContentPage ...
             xmlns:controls="clr-namespace:MyMauiControl.Controls"
             Unloaded="ContentPage_Unloaded">
    <Grid>
        <controls:CustomEntry x:Name="customEntry"
                              ... />
    </Grid>
</ContentPage>

Il gestore eventi per l'evento Unloaded può quindi richiamare il metodo nella DisconnectHandler relativa Handler istanza:

void ContentPage_Unloaded(object sender, EventArgs e)
{
    customEntry.Handler?.DisconnectHandler();
}

Disconnessione del gestore di controllo

L'implementazione del gestore di ogni piattaforma esegue l'override dell'implementazione DisconnectHandler , che viene usata per eseguire la pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti. Per impostazione predefinita, i gestori si disconnettono automaticamente dai controlli quando possibile, ad esempio quando si passa all'indietro in un'app.

In alcuni scenari può essere necessario controllare quando un gestore si disconnette dal relativo controllo, che può essere ottenuto con la HandlerProperties.DisconnectPolicy proprietà associata. Questa proprietà richiede un HandlerDisconnectPolicy argomento, con l'enumerazione che definisce i valori seguenti:

  • Automatic, che indica che il gestore verrà disconnesso automaticamente. Questo è il valore predefinito della proprietà associata HandlerProperties.DisconnectPolicy.
  • Manual, che indica che il gestore deve essere disconnesso manualmente richiamando l'implementazione DisconnectHandler() .

Nell'esempio seguente viene illustrata l'impostazione della HandlerProperties.DisconnectPolicy proprietà associata:

<controls:CustomEntry x:Name="customEntry"
                      Text="Hello world"
                      TextColor="Blue"
                      HandlerProperties.DisconnectPolicy="Manual" />             

Quando si imposta la proprietà associata su Manual è necessario richiamare manualmente l'implementazione HandlerProperties.DisconnectPolicy del DisconnectHandler gestore da una posizione appropriata nel ciclo di vita dell'app. A tale scopo, è possibile richiamare customEntry.Handler?.DisconnectHandler();.

È inoltre disponibile un DisconnectHandlers metodo di estensione che disconnette i gestori da un determinato IViewoggetto :

video.DisconnectHandlers();

Quando si disconnette, il DisconnectHandlers metodo si propaga verso il basso nell'albero dei controlli finché non viene completato o arriva a un controllo che ha impostato un criterio manuale.

Vedi anche