原生嵌入

浏览示例。 浏览示例

通常,.NET Multi-platform App UI (.NET MAUI) 应用包括包含布局的页面,例如 Grid,以及包含视图的布局,例如 Button。 页面、布局和视图全部派生自 Element。 本机嵌入使派生自 Element 的任何 .NET MAUI 控件都可以在 .NET Android、.NET iOS、.NET Mac Catalyst 和 WinUI 本机应用中使用。

在本机应用中使用 .NET MAUI 控件的过程如下所示:

  1. 创建扩展方法,以启动本机嵌入式应用。 有关详细信息,请参阅创建扩展方法
  2. 创建包含 .NET MAUI UI 和任何依赖项的 .NET MAUI 单一项目。 有关详细信息,请参阅创建 .NET MAUI 单一项目
  3. 创建本机应用,并在其中启用 .NET MAUI 支持。 有关详细信息,请参阅 启用 .NET MAUI 支持
  4. 通过调用 UseMauiEmbedding 扩展方法初始化 .NET MAUI。 有关详细信息,请参阅启用 .NET MAUI
  5. 创建 .NET MAUI UI,并使用 ToPlatformEmbedding 扩展方法将其转换为适当的本机类型。 有关详细信息,请参阅使用 .NET MAUI 控件

注意

使用本机嵌入时,.NET MAUI 的数据绑定引擎仍然有效。 但是,必须使用本机导航 API 执行页面导航。

创建扩展方法

在创建使用 .NET MAUI 控件的本机应用之前,应首先创建一个 .NET MAUI 类库项目,并从中删除平台文件夹和 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 命名空间中,用于在每个平台上启动本机嵌入式应用。 扩展方法引用 EmbeddedPlatformApplicationEmbeddedWindowHandlerEmbeddedWindowProvider 类型,还必须将其添加到 .NET MAUI 库项目。

以下代码显示了 EmbeddedPlatformApplication 类,该类应添加到与 EmbeddedExtensions 类相同的 .NET MAUI 库项目中:

#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 类,该类应添加到与 EmbeddedExtensions 类相同的 .NET MAUI 库项目中:

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 类,该类应添加到与 EmbeddedExtensions 类相同的 .NET MAUI 库项目中:

#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 单一项目

在创建使用 .NET MAUI 控件的本机应用之前,应将 .NET MAUI 应用项目添加到与之前创建的 .NET MAUI 类库项目相同的解决方案中。 .NET MAUI 应用项目将存储你打算在本机嵌入式应用中重复使用的 UI。 将新的 .NET MAUI 应用项目添加到解决方案后,请执行以下步骤:

  1. 从项目中删除属性文件夹。

  2. 从项目中删除平台文件夹。

  3. 从项目中删除资源/应用图标文件夹。

  4. 从项目中删除资源/原始文件夹。

  5. 从项目中删除资源/初始文件夹。

  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. 修改 MauiProgram 类中的 CreateMauiApp 方法,使其接受方法返回前调用的可选 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 UI 添加到项目,包括任何依赖项和资源,并确保项目正确生成。

启用 .NET MAUI 支持

若要使用派生自 .NET Android、.NET iOS、.NET Mac Catalyst 或 WinUI 应用中的 Element 的 .NET MAUI 控件,应将本机应用项目添加到之前创建的 .NET MAUI 类库项目所在的同一解决方案中。 然后,应在本机应用的项目文件中启用 .NET MAUI 支持,方法是将 $(UseMaui)$(MauiEnablePlatformUsings) 生成属性设置为在项目文件的第一个 <PropertyGroup> 节点中的 true

<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 应用,还需要将 $(EnableDefaultXamlItems) 生成属性设置为 false

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

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

这样你将不会收到有关已定义 InitializeComponent 方法的生成错误。

然后,将 $(PackageReference) 生成项添加到 Microsoft.Maui.ControlsMicrosoft.Maui.Controls.Compatiblity NuGet 包的项目文件中:

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

初始化 .NET MAUI

必须先初始化 .NET MAUI,然后本机应用项目才能构造 .NET MAUI 控件。 选择何时初始化它主要取决于在应用流中最方便的时候 - 它可以在启动时执行,也可以在构造 .NET MAUI 控件之前执行。 此处概述的方法是在创建应用的初始 UI 时初始化 .NET MAUI。

通常,在本机应用项目中初始化 .NET MAUI 的模式如下所示:

在 Android 上,MainActivity 类中的 OnCreate 替代通常是执行应用启动相关任务的位置。 下面的代码示例展示 MainActivity 类中正在初始化的 .NET MAUI:

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 上,应修改 AppDelegate 类中的 FinishedLaunching 替代,以创建主视图控制器:

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;
        }
    }
}

然后,可以在主视图控制器的 ViewDidLoad 方法中初始化 .NET MAUI:

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

            ...
        }
    }
}

在 Windows 上,MainWindow 类通常是执行 UI 相关应用启动任务的位置:

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 对象。 在 MauiAppBuilder 对象上调用 UseMauiEmbedding 扩展方法。 因此,本机应用项目应包含对你创建的包含此扩展方法的 .NET MAUI 类库项目的引用。 然后,从 MauiApp 对象创建 MauiContext 对象,利用 bool 确定上下文的范围。 将 .NET MAUI 控件转换为本机类型时,将使用 MauiContext 对象。

使用 .NET MAUI 控件

在本机应用中初始化 .NET MAUI 后,可以将 .NET MAUI UI 添加到本机应用的布局。 这可以通过创建 UI 的实例并使用 ToPlatformEmbedded 扩展方法将其转换为适当的本机类型来实现。

在 Android 上,ToPlatformEmbedded 扩展方法将 .NET MAUI 控件转换为 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 扩展方法将 .NET MAUI 控件转换为 UIView 对象:

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

在此示例中,派生自 ContentView 的对象转换为 UIView 对象。

注意

ToPlatformEmbedded 扩展方法位于前面创建的 .NET MAUI 类库中。 因此,本机应用项目应包含对该项目的引用。

然后,可以将 UIView 对象添加到视图控制器中的视图:

stackView.AddArrangedSubView(nativeView);

警告

目前无法与 iOS 和 Mac Catalyst 上的 .NET MAUI UI 进行交互。 有关详细信息,请参阅 GitHub 问题 #19340

此外,可以使用 .NET MAUI 中的 ToUIViewController 扩展方法尝试将 .NET MAUI 页转换为 UIViewController

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

在此示例中,派生自 ContentPage 的对象转换为 UIViewController

在 Windows 上,ToPlatformEmbedded 扩展方法将 .NET MAUI 控件转换为 FrameworkElement 对象:

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

在此示例中,派生自 ContentView 的对象转换为 FrameworkElement 对象。 然后,可以将 FrameworkElement 对象设置为 WinUI 页的内容。

然后,可以将 FrameworkElement 对象添加到本机应用中的布局:

stackPanel.Children.Add(nativeView);

重要

为了避免发生错误,应在调试配置中运行本机嵌入式应用之前禁用 XAML 热重载。