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:
- Creare una classe per il controllo multipiattaforma, che fornisce l'API pubblica del controllo. Per altre informazioni, vedere Creare il controllo multipiattaforma.
- Creare una
partial
classe del gestore. Per altre informazioni, vedere Creare il gestore. - 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à.
- 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. - 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à associataHandlerProperties.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.