Встроенное внедрение

Просмотрите пример. Обзор примера

Как правило, приложение многоплатформенного пользовательского интерфейса приложений .NET (.NET MAUI) включает страницы, содержащие макеты, например макеты и макеты, содержащие представления, напримерGridButton. Страницы, макеты и представления являются производными от Element. Встроенное внедрение позволяет использовать любые элементы управления MAUI .NET, производные от Element использования в .NET для Android, .NET для iOS, .NET для Mac Catalyst и собственных приложений WinUI.

Процесс использования элемента управления MAUI .NET в собственном приложении выглядит следующим образом:

  1. Создайте методы расширения для загрузки собственного внедренного приложения. Дополнительные сведения см. в разделе "Создание методов расширения".
  2. Создайте единый проект .NET MAUI, содержащий пользовательский интерфейс .NET MAUI и все зависимости. Дополнительные сведения см. в статье "Создание единого проекта .NET MAUI".
  3. Создайте собственное приложение и включите в ней поддержку .NET MAUI. Дополнительные сведения см. в разделе "Включение поддержки MAUI для .NET".
  4. Инициализация .NET MAUI путем UseMauiEmbedding вызова метода расширения. Дополнительные сведения см. в разделе Инициализация MAUI .NET.
  5. Создайте пользовательский интерфейс .NET MAUI и преобразуйте его в соответствующий ToPlatformEmbedding собственный тип с помощью метода расширения. Дополнительные сведения см. в разделе "Использование элементов управления MAUI .NET".

Примечание.

При использовании встроенного внедрения подсистема привязки данных .NET MAUI по-прежнему работает. Однако навигация по страницам должна выполняться с помощью собственного API навигации.

Создание методов расширения

Перед созданием собственного приложения, использующее элементы управления MAUI .NET, необходимо сначала создать проект библиотеки классов .NET MAUI и удалить папку Platform и Class1 класс из него. Затем добавьте в него класс с именем EmbeddedExtensions , который содержит следующий код:

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Maui.Platform;

#if ANDROID
using PlatformView = Android.Views.View;
using PlatformWindow = Android.App.Activity;
using PlatformApplication = Android.App.Application;
#elif IOS || MACCATALYST
using PlatformView = UIKit.UIView;
using PlatformWindow = UIKit.UIWindow;
using PlatformApplication = UIKit.IUIApplicationDelegate;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
using PlatformWindow = Microsoft.UI.Xaml.Window;
using PlatformApplication = Microsoft.UI.Xaml.Application;
#endif

namespace Microsoft.Maui.Controls;

public static class EmbeddedExtensions
{
    public static MauiAppBuilder UseMauiEmbedding(this MauiAppBuilder builder, PlatformApplication? platformApplication = null)
    {
#if ANDROID
        platformApplication ??= (Android.App.Application)Android.App.Application.Context;
#elif IOS || MACCATALYST
        platformApplication ??= UIKit.UIApplication.SharedApplication.Delegate;
#elif WINDOWS
        platformApplication ??= Microsoft.UI.Xaml.Application.Current;
#endif

        builder.Services.AddSingleton(platformApplication);
        builder.Services.AddSingleton<EmbeddedPlatformApplication>();
        builder.Services.AddScoped<EmbeddedWindowProvider>();

        // Returning null is acceptable here as the platform window is optional - but we don't know until we resolve it
        builder.Services.AddScoped<PlatformWindow>(svc => svc.GetRequiredService<EmbeddedWindowProvider>().PlatformWindow!);
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IMauiInitializeService, EmbeddedInitializeService>());
        builder.ConfigureMauiHandlers(handlers =>
        {
            handlers.AddHandler(typeof(Window), typeof(EmbeddedWindowHandler));
        });

        return builder;
    }

    public static IMauiContext CreateEmbeddedWindowContext(this MauiApp mauiApp, PlatformWindow platformWindow, Window? window = null)
    {
        var windowScope = mauiApp.Services.CreateScope();

#if ANDROID
        var windowContext = new MauiContext(windowScope.ServiceProvider, platformWindow);
#else
        var windowContext = new MauiContext(windowScope.ServiceProvider);
#endif

        window ??= new Window();

        var wndProvider = windowContext.Services.GetRequiredService<EmbeddedWindowProvider>();
        wndProvider.SetWindow(platformWindow, window);
        window.ToHandler(windowContext);

        return windowContext;
    }

    public static PlatformView ToPlatformEmbedded(this IElement element, IMauiContext context)
    {
        var wndProvider = context.Services.GetService<EmbeddedWindowProvider>();
        if (wndProvider is not null && wndProvider.Window is Window wnd && element is VisualElement visual)
            wnd.AddLogicalChild(visual);

        return element.ToPlatform(context);
    }

    private class EmbeddedInitializeService : IMauiInitializeService
    {
        public void Initialize(IServiceProvider services) =>
            services.GetRequiredService<EmbeddedPlatformApplication>();
    }
}

Эти методы расширения находятся в Microsoft.Maui.Controls пространстве имен и используются для загрузки собственного внедренного приложения на каждой платформе. Ссылки на EmbeddedPlatformApplicationEmbeddedWindowHandlerметоды расширения и EmbeddedWindowProvider типы, которые также необходимо добавить в проект библиотеки .NET MAUI.

В следующем коде показан EmbeddedPlatformApplication класс, который следует добавить в тот же проект библиотеки .NET MAUI, что и EmbeddedExtensions класс:

#if ANDROID
using PlatformApplication = Android.App.Application;
#elif IOS || MACCATALYST
using PlatformApplication = UIKit.IUIApplicationDelegate;
#elif WINDOWS
using PlatformApplication = Microsoft.UI.Xaml.Application;
#endif

namespace Microsoft.Maui.Controls;

internal class EmbeddedPlatformApplication : IPlatformApplication
{
    private readonly MauiContext rootContext;
    private readonly IMauiContext applicationContext;

    public IServiceProvider Services { get; }
    public IApplication Application { get; }

    public EmbeddedPlatformApplication(IServiceProvider services)
    {
        IPlatformApplication.Current = this;

#if ANDROID
        var platformApp = services.GetRequiredService<PlatformApplication>();
        rootContext = new MauiContext(services, platformApp);
#else
        rootContext = new MauiContext(services);
#endif

        applicationContext = MakeApplicationScope(rootContext);
        Services = applicationContext.Services;
        Application = Services.GetRequiredService<IApplication>();
    }

    private static IMauiContext MakeApplicationScope(IMauiContext rootContext)
    {
        var scopedContext = new MauiContext(rootContext.Services);
        InitializeScopedServices(scopedContext);
        return scopedContext;
    }

    private static void InitializeScopedServices(IMauiContext scopedContext)
    {
        var scopedServices = scopedContext.Services.GetServices<IMauiInitializeScopedService>();

        foreach (var service in scopedServices)
            service.Initialize(scopedContext.Services);
    }
}

В следующем коде показан EmbeddedWindowHandler класс, который следует добавить в тот же проект библиотеки .NET MAUI, что и EmbeddedExtensions класс:

using Microsoft.Maui.Handlers;

#if ANDROID
using PlatformWindow = Android.App.Activity;
#elif IOS || MACCATALYST
using PlatformWindow = UIKit.UIWindow;
#elif WINDOWS
using PlatformWindow = Microsoft.UI.Xaml.Window;
#endif

namespace Microsoft.Maui.Controls;

internal class EmbeddedWindowHandler : ElementHandler<IWindow, PlatformWindow>, IWindowHandler
{
    public static IPropertyMapper<IWindow, IWindowHandler> Mapper =
        new PropertyMapper<IWindow, IWindowHandler>(ElementHandler.ElementMapper)
        {
        };

    public static CommandMapper<IWindow, IWindowHandler> CommandMapper =
        new CommandMapper<IWindow, IWindowHandler>(ElementHandler.ElementCommandMapper)
        {
        };

    public EmbeddedWindowHandler() : base(Mapper)
    {
    }

    protected override PlatformWindow CreatePlatformElement() =>
        MauiContext!.Services.GetRequiredService<PlatformWindow>() ??
        throw new InvalidOperationException("EmbeddedWindowHandler could not locate a platform window.");
}

В следующем коде показан EmbeddedWindowProvider класс, который следует добавить в тот же проект библиотеки .NET MAUI, что и EmbeddedExtensions класс:

#if ANDROID
using PlatformWindow = Android.App.Activity;
#elif IOS || MACCATALYST
using PlatformWindow = UIKit.UIWindow;
#elif WINDOWS
using PlatformWindow = Microsoft.UI.Xaml.Window;
#endif

namespace Microsoft.Maui.Controls;

public class EmbeddedWindowProvider
{
    WeakReference<PlatformWindow?>? platformWindow;
    WeakReference<Window?>? window;

    public PlatformWindow? PlatformWindow => Get(platformWindow);
    public Window? Window => Get(window);

    public void SetWindow(PlatformWindow? platformWindow, Window? window)
    {
        this.platformWindow = new WeakReference<PlatformWindow?>(platformWindow);
        this.window = new WeakReference<Window?>(window);
    }

    private static T? Get<T>(WeakReference<T?>? weak) where T : class =>
        weak is not null && weak.TryGetTarget(out var target) ? target : null;
}

Создание единого проекта .NET MAUI

Перед созданием собственного приложения, использующее элементы управления MAUI .NET, необходимо добавить проект приложения .NET MAUI в то же решение, что и проект библиотеки классов .NET MAUI, созданный ранее. Проект приложения .NET MAUI будет хранить пользовательский интерфейс, который вы планируете повторно использовать в собственном внедренном приложении. После добавления нового проекта приложения MAUI .NET в решение выполните следующие действия:

  1. Удалите папку "Свойства" из проекта.

  2. Удалите папку Platform из проекта.

  3. Удалите папку Resources/AppIcon из проекта.

  4. Удалите папку Resources/raw из проекта.

  5. Удалите папку Resources/Splash из проекта.

  6. AppShell Удалите класс из проекта.

  7. Измените App класс таким образом, чтобы он не задал MainPage свойство:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
    
  8. MainPage Удалите класс из проекта.

  9. Измените файл проекта таким образом, чтобы $(TargetFrameworks) свойство сборки было задано net8.0, и $(OutputType) свойство сборки удаляется:

    <PropertyGroup>
      <TargetFrameworks>net8.0</TargetFrameworks>
    
      <RootNamespace>MyMauiApp</RootNamespace>
      <UseMaui>true</UseMaui>
      <SingleProject>true</SingleProject>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
    
      ...
    </PropertyGroup>
    
  10. CreateMauiApp Измените метод в MauiProgram классе таким образом, чтобы он принял необязательный Action<MauiAppBuilder> аргумент, который вызывается перед возвратом метода:

    public static MauiApp CreateMauiApp(Action<MauiAppBuilder>? additional = null)
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });
    
        #if DEBUG
            builder.Logging.AddDebug();
        #endif
    
        additional?.Invoke(builder);
        return builder.Build();
    }
    

На этом этапе необходимо добавить в проект необходимый пользовательский интерфейс .NET MAUI, включая все зависимости и ресурсы, и убедиться, что проект будет правильно построен.

Включение поддержки .NET MAUI

Чтобы использовать элементы управления .NET MAUI, производные от Element .NET для Android, .NET для iOS, .NET для Mac Catalyst или приложения WinUI, необходимо добавить собственный проект приложения в то же решение, что и проект библиотеки классов .NET MAUI, созданный ранее. Затем необходимо включить поддержку .NET MAUI в файле проекта собственного приложения, задав $(UseMaui) свойства true сборки $(MauiEnablePlatformUsings) в первом <PropertyGroup> узле в файле проекта:

<PropertyGroup>
    ...
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>

    <UseMaui>true</UseMaui>
    <MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>  
</PropertyGroup>

Для приложений .NET для Mac Catalyst также необходимо задать $(SupportedOSPlatformVersion) для свойства сборки не менее 14.0:

<PropertyGroup>
    ...
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>

    <SupportedOSPlatformVersion>14.2</SupportedOSPlatformVersion>
    <UseMaui>true</UseMaui>
    <MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>  
</PropertyGroup>

Для приложений WinUI также необходимо задать для свойства falseсборки $(EnableDefaultXamlItems) значение :

<PropertyGroup>
    ...
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>

    <UseMaui>true</UseMaui>
    <MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>    
    <EnableDefaultXamlItems>false</EnableDefaultXamlItems>
</PropertyGroup>

Это приведет к остановке получения ошибок сборки о том, что InitializeComponent метод уже определен.

Затем добавьте $(PackageReference) элементы сборки Microsoft.Maui.Controls в файл проекта для пакетов NuGet:Microsoft.Maui.Controls.Compatiblity

<ItemGroup>
    <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
    <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
</ItemGroup>

Инициализация .NET MAUI

.NET MAUI необходимо инициализировать, прежде чем проект собственного приложения может создать элемент управления .NET MAUI. Выбор времени инициализации в первую очередь зависит от того, когда он наиболее удобно в потоке приложений, он может выполняться при запуске или непосредственно перед созданием элемента управления MAUI .NET. Описанный здесь подход заключается в инициализации .NET MAUI при создании начального пользовательского интерфейса приложения.

Как правило, шаблон инициализации .NET MAUI в проекте собственного приложения выглядит следующим образом:

В Android OnCreate переопределение в MainActivity классе обычно является местом для выполнения связанных с приложением задач запуска приложений. В следующем примере кода показано, как инициализировать MAUI .NET в MainActivity классе:

namespace MyNativeEmbeddedApp.Droid
{
    [Activity(Label = "@string/app_name", MainLauncher = true, Theme = "@style/AppTheme")]
    public class MainActivity : Activity
    {
        public static readonly Lazy<MauiApp> MauiApp = new(() =>
        {
            var mauiApp = MauiProgram.CreateMauiApp(builder =>
            {
                builder.UseMauiEmbedding();
            });
            return mauiApp;
        });

        public static bool UseWindowContext = true;

        protected override void OnCreate(Bundle? savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Ensure .NET MAUI app is built before creating .NET MAUI views
            var mauiApp = MainActivity.MauiApp.Value;

            // Create .NET MAUI context
            var mauiContext = UseWindowContext
                ? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
                : new MauiContext(mauiApp.Services, this);  // Create app context

            ...              
        }
    }
}

В iOS и Mac Catalyst FinishedLaunching необходимо изменить переопределение в AppDelegate классе, чтобы создать основной контроллер представления:

namespace MyNativeEmbeddedApp.iOS
{
    [Register("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        public override UIWindow? Window { get; set; }

        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            Window = new UIWindow(UIScreen.MainScreen.Bounds);
            var vc = new MainViewController();
            Window.RootViewController = vc;
            Window.MakeKeyAndVisible();
            return true;
        }
    }
}

Затем .NET MAUI можно инициализировать в методе в контроллере основного ViewDidLoad представления:

using Microsoft.Maui.Platform;

namespace MyNativeEmbeddedApp.iOS
{
    public class MainViewController : UIViewController
    {
        UIWindow GetWindow() =>
            View?.Window ??
            ParentViewController?.View?.Window ??
            MainViewController.MauiApp.Value.Services.GetRequiredService<IUIApplicationDelegate>().GetWindow() ??
            UIApplication.SharedApplication.Delegate.GetWindow();

        public static readonly Lazy<MauiApp> MauiApp = new(() =>
        {
            var mauiApp = MauiProgram.CreateMauiApp(builder =>
            {
                builder.UseMauiEmbedding();
            });
            return mauiApp;
        });

        public static bool UseWindowContext = true;

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            // Ensure app is built before creating .NET MAUI views
            var mauiApp = MainViewController.MauiApp.Value;

            // Create .NET MAUI context
            var mauiContext = UseWindowContext
                ? mauiApp.CreateEmbeddedWindowContext(GetWindow()) // Create window context
                : new MauiContext(mauiApp.Services);               // Create app context

            ...
        }
    }
}

В MainWindow Windows класс обычно является местом для выполнения задач запуска приложения, связанных с пользовательским интерфейсом:

namespace MyNativeEmbeddedApp.WinUI
{
    public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
    {
        public static readonly Lazy<MauiApp> MauiApp = new(() =>
        {
            var mauiApp = MauiProgram.CreateMauiApp(builder =>
            {
                builder.UseMauiEmbedding();
            });
            return mauiApp;
        });

        public static bool UseWindowContext = true;

        public MainWindow()
        {
            this.InitializeComponent();

            // Ensure .NET MAUI app is built before creating .NET MAUI views
            var mauiApp = MainWindow.MauiApp.Value;

            // Create .NET MAUI context
            var mauiContext = UseWindowContext
                ? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
                : new MauiContext(mauiApp.Services);        // Create app context

            ...
        }
    }
}

В этом примере MauiApp объект создается с помощью отложенной инициализации. Метод UseMauiEmbedding расширения вызывается в объекте MauiAppBuilder . Поэтому проект собственного приложения должен содержать ссылку на созданный проект библиотеки классов .NET MAUI, содержащий этот метод расширения. Затем MauiContext объект создается из MauiApp объекта с определением bool того, откуда область контекст. Объект MauiContext будет использоваться при преобразовании элементов управления MAUI .NET в собственные типы.

Использование элементов управления MAUI .NET

После инициализации .NET MAUI в собственном приложении вы можете добавить пользовательский интерфейс .NET MAUI в макет собственного приложения. Это можно сделать, создав экземпляр пользовательского интерфейса и преобразовав его в соответствующий собственный тип с ToPlatformEmbedded помощью метода расширения.

В ToPlatformEmbedded Android метод расширения преобразует элемент управления MAUI .NET в объект Android View :

var mauiView = new MyMauiContent();
Android.Views.View nativeView = mauiView.ToPlatformEmbedded(mauiContext);

В этом примере производный ContentViewобъект преобразуется в объект Android View .

Примечание.

Метод ToPlatformEmbedded расширения находится в созданной ранее библиотеке классов .NET MAUI. Поэтому проект собственного приложения должен содержать ссылку на этот проект.

Затем View объект можно добавить в макет в собственном приложении:

rootLayout.AddView(nativeView, new LinearLayout.LayoutParams(MatchParent, WrapContent));

В iOS и Mac Catalyst ToPlatformEmbedded метод расширения преобразует элемент управления MAUI .NET в UIView объект:

var mauiView = new MyMauiContent();
UIView nativeView = mauiView.ToPlatformEmbedded(mauiContext);

В этом примере производный ContentViewобъект преобразуется в UIView объект.

Примечание.

Метод ToPlatformEmbedded расширения находится в созданной ранее библиотеке классов .NET MAUI. Поэтому проект собственного приложения должен содержать ссылку на этот проект.

Затем UIView объект можно добавить в представление в контроллере представления:

stackView.AddArrangedSubView(nativeView);

Предупреждение

В настоящее время невозможно взаимодействовать с пользовательским интерфейсом .NET MAUI в iOS и Mac Catalyst. Дополнительные сведения см. в статье о проблеме GitHub #19340.

Кроме того, ToUIViewController метод расширения в .NET MAUI можно использовать для преобразования страницы UIViewControllerMAUI .NET в :

MyMauiPage myMauiPage = new MyMauiPage();
UIViewController myPageController = myMauiPage.ToUIViewController(mauiContext);

В этом примере производный ContentPageобъект преобразуется в объект UIViewController.

В ToPlatformEmbedded Windows метод расширения преобразует элемент управления MAUI .NET в FrameworkElement объект:

var mauiView = new MyMauiContent();
FrameworkElement nativeView = myMauiPage.ToPlatformEmbedded(mauiContext);

В этом примере производный ContentViewобъект преобразуется в FrameworkElement объект. Затем FrameworkElement объект можно задать как содержимое страницы WinUI.

Затем FrameworkElement объект можно добавить в макет в собственном приложении:

stackPanel.Children.Add(nativeView);

Внимание

Чтобы избежать ошибки, перед запуском собственного внедренного приложения в конфигурации отладки необходимо отключить горячую перезагрузку XAML.