原生嵌入

浏览示例。 浏览示例

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

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

  1. 创建扩展方法,以启动本机嵌入式应用。 有关详细信息,请参阅创建扩展方法
  2. 创建包含 .NET MAUI UI 和任何依赖项的 .NET MAUI 单一项目。 有关详细信息,请参阅创建 .NET MAUI 单一项目
  3. 创建本机应用,并在其中启用 .NET MAUI 支持。 有关详细信息,请参阅 启用 .NET MAUI 支持
  4. 在本机应用项目中初始化 .NET MAUI。 有关详细信息,请参阅启用 .NET MAUI
  5. 创建 .NET MAUI UI,并使用 ToPlatformEmbedding 扩展方法将其转换为适当的本机类型。 有关详细信息,请参阅使用 .NET MAUI 控件
  1. 创建包含 .NET MAUI UI 和任何依赖项的 .NET MAUI 单一项目。 有关详细信息,请参阅创建 .NET MAUI 单一项目
  2. 创建本机应用,并在其中启用 .NET MAUI 支持。 有关详细信息,请参阅 启用 .NET MAUI 支持
  3. 在本机应用项目中初始化 .NET MAUI。 有关详细信息,请参阅启用 .NET MAUI
  4. 创建 .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属性或重写CreateWindow方法:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
    
  8. 从项目中删除 MainPage 类。

  9. 修改项目文件,使 $(TargetFramework) 生成属性设置为 net8.0,并删除 $(OutputType) 生成属性:

    <PropertyGroup>
      <TargetFramework>net8.0</TargetFramework>
    
      <RootNamespace>MyMauiApp</RootNamespace>
      <UseMaui>true</UseMaui>
      <SingleProject>true</SingleProject>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
    
      ...
    </PropertyGroup>
    

    重要

    确保设置 $(TargetFramework) 生成属性,而不是 $(TargetFrameworks) 生成属性。

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

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

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

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

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

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

  6. 从项目中删除 AppShell 类。

  7. App确保类未设置MainPage属性或重写CreateWindow方法:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
    
  8. 从项目中删除 MainPage 类。

  9. 修改项目文件,使 $(TargetFramework) 生成属性设置为 net9.0,并删除 $(OutputType) 生成属性:

    <PropertyGroup>
      <TargetFramework>net9.0</TargetFramework>
    
      <RootNamespace>MyMauiApp</RootNamespace>
      <UseMaui>true</UseMaui>
      <SingleProject>true</SingleProject>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
    
      ...
    </PropertyGroup>
    

    重要

    确保设置 $(TargetFramework) 生成属性,而不是 $(TargetFrameworks) 生成属性。

  10. 在类中MauiProgramCreateMauiApp,修改方法以接受TApp泛型参数,并接受在方法返回之前调用的可选Action<MauiAppBuilder>参数。 此外,将调用从以下项 UseMauiApp<App> 更改为 UseMauiEmbeddedApp<TApp>

    public static class MauiProgram
    {
        // Create a MauiApp using the specified application.
        public static MauiApp CreateMauiApp<TApp>(Action<MauiAppBuilder>? additional = null) where TApp : App
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiEmbeddedApp<TApp>()
                .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();
        }
    }
    
  11. MauiProgram 类中,添加接受 CreateMauiApp 可选 Action<MauiAppBuilder> 参数的重载:

    public static class MauiProgram
    {
        ...
    
        // Create a MauiApp using the default application.
        public static MauiApp CreateMauiApp(Action<MauiAppBuilder>? additional = null) =>
            CreateMauiApp<App>(additional);
    }
    

然后,应将所需的 .NET MAUI UI 添加到项目,包括任何依赖项和资源,并确保项目正确生成。

启用 .NET MAUI 支持

若要在 .NET for Android、.NET for iOS、.NET for 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 for Mac Catalyst 应用,还需要将 $(SupportedOSPlatformVersion) 内部版本属性设置为至少 14.0:

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

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

对于 .NET for Mac Catalyst 应用,还需要将 $(SupportedOSPlatformVersion) 生成属性设置为至少 15.0:

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

    <SupportedOSPlatformVersion>15.0</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>

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

<ItemGroup>
    <PackageReference Include="Microsoft.Maui.Controls" 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 context = UseWindowContext
            ? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
            : new MauiContext(mauiApp.Services, this);  // Create app context

        ...              
    }
}

在 iOS 和 Mac Catalyst 上,应修改 AppDelegate 类,以便为 FinishedLaunching 替代返回 true

namespace MyNativeEmbeddedApp.iOS;

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

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) => true;
}

应修改 SceneDelegate 类中的 WillConnect 方法,以创建主视图控制器并将其设置为 UINavigationController 的视图:

namespace MyNativeEmbeddedApp.iOS;

[Register("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
    [Export("window")]
    public UIWindow? Window { get; set; }

    [Export("scene:willConnectToSession:options:")]
    public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
    {
        if (scene is not UIWindowScene windowScene)
            return;

        Window = new UIWindow(windowScene);

        var mainVC = new MainViewController();
        var navigationController = new UINavigationController(mainVC);
        navigationController.NavigationBar.PrefersLargeTitles = true;

        Window.RootViewController = navigationController;
        Window.MakeKeyAndVisible();
    }

    ...
}

然后,在 XML 编辑器中打开 Info.plist 文件,并将以下 XML 项添加到文件结尾:

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <true/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Default Configuration</string>
        <key>UISceneDelegateClassName</key>
        <string>SceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

然后,可以在主视图控制器的 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 context = 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 context = 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 兼容性,应在窗口上下文中执行嵌入。

应用上下文

可以在应用上下文中执行本机嵌入,其中本机应用不知道窗口。 使用此方法,本机嵌入初始化需要:

以下示例演示了此方法:

var mauiApp = MauiProgram.CreateMauiApp();
var context = new MauiContext(mauiApp.Services); // Activity also needs passing on Android

然后,可以使用扩展方法创建 .NET MAUI 视图并将其转换为本机视图 ToPlatformEmbedded ,该方法需要对象 MauiContext 作为参数。

此方法适用于本机应用需要嵌入简单的 .NET MAUI UI 但不需要访问所有 .NET MAUI 功能的方案。 此方法的缺点是,诸如热重载和某些 .NET MAUI 功能之类的工具将不起作用。

提示

不建议每次将 .NET MAUI 视图嵌入为本机视图时创建 MauiApp 对象。 如果嵌入视图访问属性, Application.Current 则这可能导致问题。 相反, MauiApp 可以将对象创建为共享的静态实例:

public static class MyEmbeddedMauiApp
{
    static MauiApp? _shared;
    public static MauiApp Shared => _shared ??= MauiProgram.CreateMauiApp();
}

使用此方法,可以在应用生命周期的早期实例化 MauiApp 对象,以避免在首次在应用中嵌入 .NET MAUI 视图时出现少量延迟。

在 Android 上,片段表示活动中的 UI 的一部分。 以下代码示例演示片段中正在初始化的 .NET MAUI:

using Android.Runtime;
using Android.Views;
using AndroidX.Navigation.Fragment;
using Microsoft.Maui.Controls.Embedding;
using Fragment = AndroidX.Fragment.App.Fragment;
using View = Android.Views.View;

namespace MyNativeEmbeddedApp.Droid;

[Register("com.companyname.nativeembeddingdemo." + nameof(FirstFragment))]
public class FirstFragment : Fragment
{
    public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) =>
        inflater.Inflate(Resource.Layout.fragment_first, container, false);

    public override void OnViewCreated(View view, Bundle? savedInstanceState)
    {
        base.OnViewCreated(view, savedInstanceState);

        // Ensure .NET MAUI app is built before creating .NET MAUI views
        var mauiApp = MyEmbeddedMauiApp.Shared;

        // Create .NET MAUI context
        var context = new MauiContext(mauiApp.Services, Activity);

        ...
    }
}

在 iOS 和 Mac Catalyst 上,应修改 AppDelegate 类,以便为 FinishedLaunching 替代返回 true

namespace MyNativeEmbeddedApp.iOS;

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

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) => true;
}

应修改 SceneDelegate 类中的 WillConnect 方法,以创建主视图控制器并将其设置为 UINavigationController 的视图:

namespace MyNativeEmbeddedApp.iOS;

[Register("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
    [Export("window")]
    public UIWindow? Window { get; set; }

    [Export("scene:willConnectToSession:options:")]
    public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
    {
        if (scene is not UIWindowScene windowScene)
            return;

        Window = new UIWindow(windowScene);

        var mainVC = new MainViewController();
        var navigationController = new UINavigationController(mainVC);
        navigationController.NavigationBar.PrefersLargeTitles = true;

        Window.RootViewController = navigationController;
        Window.MakeKeyAndVisible();
    }

    ...
}

然后,在 XML 编辑器中打开 Info.plist 文件,并将以下 XML 项添加到文件结尾:

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <true/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Default Configuration</string>
        <key>UISceneDelegateClassName</key>
        <string>SceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

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

using Microsoft.Maui.Controls.Embedding;

namespace MyNativeEmbeddedApp.iOS;

public class MainViewController : UIViewController
{
    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        // Ensure .NET MAUI app is built before creating .NET MAUI views
        var mauiApp = MyEmbeddedMauiApp.Shared;

        // Create .NET MAUI context
        var context = new MauiContext(mauiApp.Services);

        ...
    }
}

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

using Microsoft.Maui.Controls.Embedding;
using Microsoft.UI.Xaml;

namespace MyNativeEmbeddedApp.WinUI;

public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
{
    public MainWindow()
    {
        this.InitializeComponent();
    }

    private async void OnRootLayoutLoaded(object? sender, RoutedEventArgs e)
    {
        await Task.Yield();

        // Ensure .NET MAUI app is built before creating .NET MAUI views
        var mauiApp = MyEmbeddedMauiApp.Shared;

        // Create .NET MAUI context
        var context = new MauiContext(mauiApp.Services);

        ...
    }
}

在此示例中,对象 MauiApp 创建为共享的静态实例。 创建此对象时, MauiProgram.CreateMauiApp 将调用该对象,从而调用 UseMauiEmbeddingMauiAppBuilder 对象上的扩展方法。 因此,本机应用项目应包含对创建的 .NET MAUI 类库项目的引用,该项目包含类 MauiProgram 和 .NET MAUI UI。 然后,从MauiApp对象创建一个MauiContext对象,该对象的范围限定为MauiApp对象。 将 .NET MAUI 控件转换为本机类型时,将使用 MauiContext 对象。

窗口上下文

可以在窗口上下文中执行本机嵌入,其中本机应用了解窗口。 在某些情况下,.NET MAUI 视图需要访问窗口才能正常工作。 例如,自适应触发器需要访问视图的窗口,如果没有窗口,则它们不起作用。

使用此方法,本机嵌入初始化需要:

  • 创建 MauiApp 对象。
  • MauiContext使用CreateEmbeddedWindowContext该方法创建对象。 该 MauiContext 对象将用于从 .NET MAUI 视图获取本机视图。

该方法 CreateEmbeddedWindowContext 创建将本机窗口与 .NET MAUI 窗口关联的窗口上下文:

var mauiApp = MauiProgram.CreateMauiApp();
var context = mauiApp.CreateEmbeddedWindowContext(this);

然后,可以使用扩展方法创建 .NET MAUI 视图并将其转换为本机视图 ToPlatformEmbedded ,该方法需要对象 MauiContext 作为参数。

注意

ToPlatformEmbedded扩展方法具有一个重载,用于向嵌入窗口添加 .NET MAUI 视图。

此方法的优点是,每个本机窗口都有一个 .NET MAUI 窗口,与窗口相关的 API 将正常工作,并且热重载等工具正常工作。

提示

不建议每次将 .NET MAUI 视图嵌入为本机视图时创建 MauiApp 对象。 如果嵌入视图访问属性, Application.Current 则这可能导致问题。 相反, MauiApp 可以将对象创建为共享的静态实例:

public static class MyEmbeddedMauiApp
{
    static MauiApp? _shared;
    public static MauiApp Shared => _shared ??= MauiProgram.CreateMauiApp();
}

使用此方法,可以在应用生命周期的早期实例化 MauiApp 对象,以避免在首次在应用中嵌入 .NET MAUI 视图时出现少量延迟。

在 Android 上,片段表示活动中的 UI 的一部分。 以下代码示例演示片段中正在初始化的 .NET MAUI:

using Android.Runtime;
using Android.Views;
using AndroidX.Navigation.Fragment;
using Microsoft.Maui.Controls.Embedding;
using Fragment = AndroidX.Fragment.App.Fragment;
using View = Android.Views.View;

namespace MyNativeEmbeddedApp.Droid;

[Register("com.companyname.nativeembeddingdemo." + nameof(FirstFragment))]
public class FirstFragment : Fragment
{
    Activity? _window;
    IMauiContext? _windowContext;

    public IMauiContext WindowContext =>
        _windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());

    public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) =>
        inflater.Inflate(Resource.Layout.fragment_first, container, false);

    public override void OnViewCreated(View view, Bundle? savedInstanceState)
    {
        base.OnViewCreated(view, savedInstanceState);

        _window ??= Activity;

        // Create MAUI embedded window context
        var context = WindowContext;

        ...
    }
}

在 iOS 和 Mac Catalyst 上,应修改 AppDelegate 类,以便为 FinishedLaunching 替代返回 true

namespace MyNativeEmbeddedApp.iOS;

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

    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) => true;
}

应修改 SceneDelegate 类中的 WillConnect 方法,以创建主视图控制器并将其设置为 UINavigationController 的视图:

namespace MyNativeEmbeddedApp.iOS;

[Register("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
    [Export("window")]
    public UIWindow? Window { get; set; }

    [Export("scene:willConnectToSession:options:")]
    public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
    {
        if (scene is not UIWindowScene windowScene)
            return;

        Window = new UIWindow(windowScene);

        var mainVC = new MainViewController();
        var navigationController = new UINavigationController(mainVC);
        navigationController.NavigationBar.PrefersLargeTitles = true;

        Window.RootViewController = navigationController;
        Window.MakeKeyAndVisible();
    }

    ...
}

然后,在 XML 编辑器中打开 Info.plist 文件,并将以下 XML 项添加到文件结尾:

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <true/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Default Configuration</string>
        <key>UISceneDelegateClassName</key>
        <string>SceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

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

using Microsoft.Maui.Controls.Embedding;

namespace MyNativeEmbeddedApp.iOS;

public class MainViewController : UIViewController
{
    UIKit.UIWindow? _window;
    IMauiContext? _windowContext;

    public IMauiContext WindowContext =>
        _windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());

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

        _window ??= ParentViewController!.View!.Window;

        // Create MAUI embedded window context
        var context = WindowContext;

        ...
    }
}

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

using Microsoft.Maui.Controls.Embedding;
using Microsoft.UI.Xaml;

namespace MyNativeEmbeddedApp.WinUI;

public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
{
    Microsoft.UI.Xaml.Window? _window;
    IMauiContext? _windowContext;

    public IMauiContext WindowContext =>
        _windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());

    public MainWindow()
    {
        this.InitializeComponent();
        _window ??= this;
    }

    private async void OnRootLayoutLoaded(object? sender, RoutedEventArgs e)
    {
        await Task.Yield();

        // Create MAUI embedded window context
        var context = WindowContext;

        ...
    }
}

在此示例中,对象 MauiApp 创建为共享的静态实例。 创建此对象时, MauiProgram.CreateMauiApp 将调用该对象,从而调用 UseMauiEmbeddingMauiAppBuilder 对象上的扩展方法。 因此,本机应用项目应包含对创建的 .NET MAUI 类库项目的引用,该项目包含类 MauiProgram 和 .NET MAUI UI。 MauiContext然后使用范围限定为窗口的方法创建CreateEmbeddedWindowContext对象。 将 .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(context);

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

注意

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

注意

扩展 ToPlatformEmbedded 方法位于命名空间中 Microsoft.Maui.Controls.Embedding 。 因此,本机应用项目应包含该命名空间的 using 语句。

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

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

在 iOS 和 Mac Catalyst 上,ToPlatformEmbedded 扩展方法将 .NET MAUI 控件转换为 UIView 对象:

var mauiView = new MyMauiContent();
UIView nativeView = mauiView.ToPlatformEmbedded(context);
nativeView.WidthAnchor.ConstraintEqualTo(View.Frame.Width).Active = true;
nativeView.HeightAnchor.ConstraintEqualTo(500).Active = true;

在此示例中,派生自 ContentView 的对象会被转换为 UIView 对象,然后你可设置其宽度和高度约束以允许交互。

注意

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

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

stackView.AddArrangedSubView(nativeView);
var mauiView = new MyMauiContent();
UIView nativeView = mauiView.ToPlatformEmbedded(context);

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

注意

扩展 ToPlatformEmbedded 方法位于命名空间中 Microsoft.Maui.Controls.Embedding 。 因此,本机应用项目应包含该命名空间的 using 语句。

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

stackView.AddArrangedSubView(new ContainerView(nativeView));

ContainerView 是一种自定义类型,用于包装 .NET MAUI 视图,以确保其大小正确。 这是通过重定向 IntrinsicContentSize 到 .NET MAUI 视图实现的 SizeThatFits

class ContainerView : UIView
{
    public ContainerView(UIView view)
    {
        AddSubview(view);
    }

    public override CGSize IntrinsicContentSize =>
        SizeThatFits(new CGSize(nfloat.MaxValue, nfloat.MaxValue));

    public override CGSize SizeThatFits(CGSize size) =>
        Subviews?.FirstOrDefault()?.SizeThatFits(size) ?? CGSize.Empty;

    public override void LayoutSubviews()
    {
        if (Subviews?.FirstOrDefault() is { } view)
            view.Frame = Bounds;
    }

    public override void SetNeedsLayout()
    {
        base.SetNeedsLayout();
          InvalidateIntrinsicContentSize();
    }
}

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

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

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

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

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

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

注意

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

注意

扩展 ToPlatformEmbedded 方法位于命名空间中 Microsoft.Maui.Controls.Embedding 。 因此,本机应用项目应包含该命名空间的 using 语句。

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

stackPanel.Children.Add(nativeView);

重要

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

支持 XAML 热重载

本机嵌入式应用中不支持 XAML 热重载。 但是,仍可以使用 XAML 热重载通过创建使用 .NET MAUI UI 的 .NET MAUI 应用来快速循环访问 .NET MAUI UI。

若要使用 XAML 热重载查看 .NET MAUI UI,请执行以下操作:

  1. 在包含 .NET MAUI UI 的项目中,更新 MauiProgram 类以添加 CreateMauiApp 重载,并修改现有 CreateMauiApp 方法以接受泛型参数:

    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp(Action<MauiAppBuilder>? additional = null) =>
            CreateMauiApp<App>(additional);
    
        public static MauiApp CreateMauiApp<TApp>(Action<MauiAppBuilder>? additional = null) where TApp : App
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<TApp>()
                .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();
        }
    }
    
  2. 在包含 .NET MAUI UI 的项目中,将每个资源字典从独立 XAML 文件转换为由代码隐藏文件支持的资源字典。

  3. 在包含 .NET MAUI UI 的项目中,更新资源字典实例化(通常在 App.xaml 中),以便 Source 属性还指定包含资源字典的程序集:

    <ResourceDictionary Source="Resources/Styles/Colors.xaml;assembly=NativeEmbeddingDemo" />
    <ResourceDictionary Source="Resources/Styles/Styles.xaml;assembly=NativeEmbeddingDemo" />
    
  4. 创建新的 .NET MAUI 应用,并将其添加到包含 .NET MAUI UI 项目和本机嵌入式应用的解决方案。

  5. 在 .NET MAUI 应用项目中,添加对包含 .NET MAUI UI 的项目的引用。

  6. 在 .NET MAUI 应用项目中,删除 .NET MAUI UI 项目提供资源的任何 Resource 子文件夹。 例如,如果 .NET MAUI UI 项目包含 Resources > FontsResources > Images 以及 Resources > Styles 文件夹,则应从刚刚创建的 .NET MAUI 应用中删除这些文件夹。 这使 .NET MAUI 应用能够使用包含 .NET MAUI UI 的项目中的资源。

  7. 在 .NET MAUI 应用中,更新 App 类,使其派生自 .NET MAUI UI 项目中的 App 类:

    <myMauiUIProject:App xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                         xmlns:myMauiUIProject="clr-namespace:NativeEmbeddingDemo;assembly=NativeEmbeddingDemo"
                         x:Class="TestHarnessApp.TestApp">
        <myMauiUIProject:App.Resources>
            <!-- App specific resources go here -->
        </myMauiUIProject:App.Resources>
    </myMauiUIProject:App>
    

    然后更新 App 类的代码隐藏文件,使其派生自 .NET MAUI UI 项目中的 App 类,并从此项目加载任何 XAML 资源:

    public partial class TestApp : myMauiUIProject.App
    {
        public TestApp()
        {
            var baseResources = Resources;
            InitializeComponent();
            Resources.MergedDictionaries.Add(baseResources);
            MainPage = new HostPage();
        }
    }
    
  8. 在 .NET MAUI 应用中,添加一个页面,显示包含 .NET MAUI UI 的项目中的 UI:

    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:myMauiUIProject="clr-namespace:NativeEmbeddingDemo;assembly=NativeEmbeddingDemo"
                 x:Class="TestHarnessApp.HostPage"
                 Title="HostPage">
        <myMauiUIProject:MyMauiContent />
    </ContentPage>
    
  9. 在 .NET MAUI 应用中,更新 MauiProgram 类以调用包含 .NET MAUI UI 的项目中的 CreateMauiApp 方法:

    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp() =>
            NativeEmbeddingDemo.MauiProgram.CreateMauiApp<TestApp>(builder =>
            {
                // Add any test harness configuration such as service stubs or mocks.
            });
    }
    

现在,你应该能够在每个平台上运行 .NET MAUI 应用项目,并使用 XAML 热重载循环访问 .NET MAUI UI。

有关此方法的示例,请参阅示例应用