Перенос пользовательского отрисовщика Xamarin.Forms в обработчик .NET MAUI
В Xamarin.Forms настраиваемые отрисовщики можно использовать для настройки внешнего вида и поведения элемента управления и создания новых кроссплатформенных элементов управления. Каждый пользовательский отрисовщик имеет ссылку на кроссплатформенный элемент управления и часто используется INotifyPropertyChanged
для отправки уведомлений об изменении свойств. Вместо использования пользовательских отрисовщиков пользовательский интерфейс приложений .NET (.NET MAUI) представляет новую концепцию, называемую обработчиком.
Обработчики предлагают множество улучшений производительности по сравнению с пользовательскими отрисовщиками. В Xamarin.Forms ViewRenderer
класс создает родительский элемент. Например, в Android создается объект, ViewGroup
который используется для вспомогательных задач размещения. В .NET MAUI ViewHandler<TVirtualView,TPlatformView> класс не создает родительский элемент, который помогает уменьшить размер визуальной иерархии и повысить производительность приложения. Обработчики также отделяют элементы управления платформой от платформы. Элемент управления платформой должен обрабатывать только потребности платформы. Это не только более эффективно, но гораздо проще расширить или переопределить при необходимости. Обработчики также подходят для повторного использования другими платформами, такими как Комета и Сказочная. Дополнительные сведения о обработчиках см. в разделе "Обработчики".
В Xamarin.Forms метод в пользовательском отрисовщике создает элемент управления платформой, инициализирует значения по умолчанию, подписывается на события и обрабатывает элемент Xamarin.Forms OnElementChanged
, к которому был присоединен отрисовщик (OldElement
) и элемент, к которому присоединен отрисовщик (NewElement
). Кроме того, один OnElementPropertyChanged
метод определяет операции, вызываемые при изменении свойства в кроссплатформенных элементах управления. .NET MAUI упрощает этот подход, чтобы каждое изменение свойства обрабатывалось отдельным методом, и поэтому код для создания элемента управления платформы, выполнения настройки элемента управления и выполнения очистки элементов управления отделяется от различных методов.
Процесс переноса пользовательского элемента управления Xamarin.Forms, поддерживаемого пользовательскими отрисовщиками на каждой платформе, в пользовательский элемент управления .NET MAUI, поддерживаемый обработчиком на каждой платформе, выглядит следующим образом:
- Создайте класс для кроссплатформенного элемента управления, который предоставляет общедоступный API элемента управления. Дополнительные сведения см. в разделе "Создание кроссплатформенного элемента управления".
- Создайте класс обработчика
partial
. Дополнительные сведения см. в разделе "Создание обработчика". - В классе обработчика создайте PropertyMapper словарь, определяющий действия, которые необходимо предпринять при изменении кроссплатформенного свойства. Дополнительные сведения см. в разделе "Создание схемы свойств".
- Создайте
partial
классы обработчика для каждой платформы, создающей собственные представления, реализующие кроссплатформенный элемент управления. Дополнительные сведения см. в разделе "Создание элементов управления платформы". - Зарегистрируйте обработчик с помощью ConfigureMauiHandlers методов AddHandler и методов в классе приложения
MauiProgram
. Дополнительные сведения см. в разделе "Регистрация обработчика".
Затем можно использовать кроссплатформенный элемент управления. Дополнительные сведения см. в разделе "Использование кроссплатформенного элемента управления".
Кроме того, пользовательские отрисовщики, которые настраивают элементы управления Xamarin.Forms, можно преобразовать таким образом, чтобы они изменяли обработчики MAUI .NET. Дополнительные сведения см. в разделе "Настройка элементов управления с помощью обработчиков".
Создание кроссплатформенного элемента управления
Чтобы создать кроссплатформенный элемент управления, необходимо создать класс, производный от 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); }
}
}
}
Элемент управления должен предоставить общедоступный API, к которому будет обращаться его обработчик, и управлять потребителями. Кроссплатформенные элементы управления должны быть производными от Viewэлемента, представляющего визуальный элемент, используемый для размещения макетов и представлений на экране.
Создание обработчика
После создания кроссплатформенного элемента управления необходимо создать partial
класс для обработчика:
#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
{
}
}
Класс обработчика — это частичный класс, реализация которого будет завершена на каждой платформе с дополнительным частичным классом.
using
Условные операторы определяют PlatformView
тип на каждой платформе. Окончательный условный using
оператор определяет PlatformView
, равный System.Object
. Это необходимо, чтобы PlatformView
тип можно было использовать в обработчике для использования на всех платформах. Альтернативой будет определение PlatformView
свойства один раз на каждую платформу с помощью условной компиляции.
Создание схемы свойств
Каждый обработчик обычно предоставляет средство сопоставления свойств, которое определяет действия, которые необходимо предпринять при изменении свойства в кроссплатформенной элементе управления. Тип PropertyMapper — это Dictionary
тип, который сопоставляет свойства кроссплатформенного элемента управления с связанными действиями.
Примечание.
Средство сопоставления свойств — это замена OnElementPropertyChanged
метода в пользовательском отрисовщике Xamarin.Forms.
PropertyMapper определяется в классе .NET MAUI ViewHandler<TVirtualView,TPlatformView> и требуется предоставить два универсальных аргумента:
- Класс для кроссплатформенного элемента управления, производный от View.
- Класс обработчика.
В следующем примере кода показан класс, расширенный CustomEntryHandler
с определением PropertyMapper :
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)
{
}
}
Dictionary
Это PropertyMapper ключ, ключ которого является string
универсальнымAction
. Представляет string
имя свойства кроссплатформенного элемента управления и Action
представляет static
метод, который требует обработчика и кроссплатформенного элемента управления в качестве аргументов. Например, сигнатурой MapText
метода является public static void MapText(CustomEntryHandler handler, CustomEntry view)
.
Каждый обработчик платформы должен предоставлять реализации действий, которые управляют API собственного представления. Это гарантирует, что при установке свойства на кроссплатформенный элемент управления базовое собственное представление будет обновлено по мере необходимости. Преимущество этого подхода заключается в том, что это позволяет легко настраивать кроссплатформенный элемент управления, так как средство сопоставления свойств может быть изменено потребителями кроссплатформенных элементов управления без подклассов. Дополнительные сведения см. в разделе "Настройка элементов управления с помощью обработчиков".
Создание элементов управления платформы
После создания карт для обработчика необходимо предоставить реализации обработчика на всех платформах. Это можно сделать, добавив реализации обработчика частичного класса в дочерние папки папки Platform . Кроме того, вы можете настроить проект для поддержки многонацеливания на основе файлов или многонацеливания на основе папок или обоих.
Многонацелие на основе файла настраивается путем добавления следующего XML-файла в файл проекта в качестве дочерних <Project>
элементов узла:
<!-- 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>
Дополнительные сведения о настройке нескольких целевых моделей см. в разделе "Настройка многонацеливания".
Каждый класс обработчика платформы должен быть частичным и производным от ViewHandler<TVirtualView,TPlatformView> класса, для которого требуется два аргумента типа:
- Класс для кроссплатформенного элемента управления, производный от View.
- Тип собственного представления, реализующего кроссплатформенный элемент управления на платформе. Это должно быть идентично типу
PlatformView
свойства в обработчике.
Внимание
Класс ViewHandler<TVirtualView,TPlatformView> предоставляет VirtualView
и PlatformView
свойства. Свойство VirtualView
используется для доступа к кроссплатформенным элементу управления из обработчика. Свойство PlatformView
используется для доступа к собственному представлению на каждой платформе, реализующей кроссплатформенный элемент управления.
Каждая из реализаций обработчика платформы должна переопределить следующие методы:
- CreatePlatformView, который должен создавать и возвращать собственное представление, реализующее кроссплатформенный элемент управления.
- ConnectHandler, который должен выполнять любую настройку собственного представления, например инициализацию собственного представления и выполнение подписок на события.
- DisconnectHandler, который должен выполнять очистку собственного представления, например отмену подписки на события и удаление объектов. Этот метод намеренно не вызывается .NET MAUI. Вместо этого необходимо вызвать его самостоятельно из подходящего расположения в жизненном цикле приложения. Дополнительные сведения см. в разделе "Очистка собственного представления".
- CreatePlatformView, который должен создавать и возвращать собственное представление, реализующее кроссплатформенный элемент управления.
- ConnectHandler, который должен выполнять любую настройку собственного представления, например инициализацию собственного представления и выполнение подписок на события.
- DisconnectHandler, который должен выполнять очистку собственного представления, например отмену подписки на события и удаление объектов. Этот метод автоматически вызывается .NET MAUI по умолчанию, хотя это поведение может быть изменено. Дополнительные сведения см. в разделе "Отключение обработчика элементов управления".
Примечание.
Метод CreatePlatformView, ConnectHandlerа также DisconnectHandler переопределения — это замена OnElementChanged
метода в пользовательском отрисовщике Xamarin.Forms.
Каждый обработчик платформы также должен реализовывать действия, определенные в словарях mapper. Кроме того, каждый обработчик платформы также должен предоставлять код, как это необходимо, для реализации функциональности кроссплатформенного элемента управления на платформе. Кроме того, для более сложных элементов управления это может быть предоставлено дополнительным типом.
В следующем примере показана CustomEntryHandler
реализация в 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
производный от ViewHandler<TVirtualView,TPlatformView> класса, с универсальным CustomEntry
аргументом, указывающим кроссплатформенный тип элемента управления, и AppCompatEditText
аргумент, указывающий тип собственного элемента управления.
Переопределение CreatePlatformView создает и возвращает AppCompatEditText
объект. Переопределение ConnectHandler — это расположение для выполнения любой требуемой настройки собственного представления. DisconnectHandler Переопределение — это расположение для выполнения любой очистки собственного представления, поэтому вызывает Dispose
метод в экземпляреAppCompatEditText
.
Обработчик также реализует действия, определенные в словаре сопоставления свойств. Каждое действие выполняется в ответ на изменение свойства кроссплатформенного элемента управления и является static
методом, который требует обработчика и межплатформенных экземпляров управления в качестве аргументов. В каждом случае действие вызывает методы, определенные в собственном элементе управления.
Регистрация обработчика
Пользовательский элемент управления и его обработчик должны быть зарегистрированы в приложении, прежде чем его можно будет использовать. Это должно произойти в CreateMauiApp
MauiProgram
классе в проекте приложения, который является кроссплатформенной точкой входа для приложения:
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();
}
}
Обработчик регистрируется в и AddHandler методеConfigureMauiHandlers. Первым аргументом метода AddHandler является кроссплатформенный тип элемента управления, а второй аргумент является его типом обработчика.
Примечание.
Этот подход регистрации позволяет избежать сканирования сборок Xamarin.Forms, что является медленным и дорогостоящим.
Использование кроссплатформенного элемента управления
После регистрации обработчика в приложении можно использовать кроссплатформенный элемент управления:
<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>
Очистка собственного представления
Реализация обработчика каждой платформы переопределяет DisconnectHandler реализацию, которая используется для очистки собственного представления, например отмены подписки на события и удаления объектов. Однако это переопределение намеренно не вызывается .NET MAUI. Вместо этого необходимо вызвать его самостоятельно из подходящего расположения в жизненном цикле приложения. Это может произойти, когда страница, содержащая элемент управления, перемещается от нее, что приводит к возникновению события страницы Unloaded
.
Обработчик событий для события страницы Unloaded
можно зарегистрировать в XAML:
<ContentPage ...
xmlns:controls="clr-namespace:MyMauiControl.Controls"
Unloaded="ContentPage_Unloaded">
<Grid>
<controls:CustomEntry x:Name="customEntry"
... />
</Grid>
</ContentPage>
Затем обработчик событий для Unloaded
события может вызвать DisconnectHandler метод в своем Handler
экземпляре:
void ContentPage_Unloaded(object sender, EventArgs e)
{
customEntry.Handler?.DisconnectHandler();
}
Отключение обработчика управления
Реализация обработчика каждой платформы переопределяет DisconnectHandler реализацию, которая используется для очистки собственного представления, например отмены подписки на события и удаления объектов. По умолчанию обработчики автоматически отключают от своих элементов управления, например при переходе назад в приложении.
В некоторых сценариях может потребоваться контролировать, когда обработчик отключается от его элемента управления, что может быть достигнуто с присоединенным свойством HandlerProperties.DisconnectPolicy
. Для этого свойства требуется HandlerDisconnectPolicy аргумент с перечислением, определяющим следующие значения:
Automatic
, указывающее, что обработчик будет отключен автоматически. Это значение по умолчанию для присоединенного свойстваHandlerProperties.DisconnectPolicy
.Manual
, указывающее, что обработчику придется отключить вручную, вызвав реализацию DisconnectHandler() .
В следующем примере показано задание присоединенного HandlerProperties.DisconnectPolicy
свойства:
<controls:CustomEntry x:Name="customEntry"
Text="Hello world"
TextColor="Blue"
HandlerProperties.DisconnectPolicy="Manual" />
При задании присоединенного HandlerProperties.DisconnectPolicy
свойства Manual
необходимо вызвать реализацию обработчика DisconnectHandler самостоятельно из подходящего расположения в жизненном цикле приложения. Это можно достичь путем customEntry.Handler?.DisconnectHandler();
вызова.
Кроме того, существует DisconnectHandlers метод расширения, который отключает обработчики от заданного:IView
video.DisconnectHandlers();
При отключении метод будет распространяться по дереву управления до тех пор, DisconnectHandlers пока он не завершится или не появится в элементе управления, настроив политику вручную.