Share via


原生內嵌

流覽範例。 流覽範例

一般而言,.NET 多平臺應用程式 UI (.NET MAUI) 應用程式包含包含版面配置的頁面,例如 Grid,以及包含檢視的版面配置,例如 Button。 頁面、版面配置和檢視全都衍生自 Element。 原生內嵌可讓衍生自 Element 的任何 .NET MAUI 控件在適用於 Android 的 .NET、適用於 iOS 的 .NET、適用於 Mac Catalyst 的 .NET 和 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. 從專案刪除 Properties 資料夾。

  2. 從項目刪除 [平臺] 資料夾。

  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 UI 新增至專案,包括任何相依性和資源,並確保專案正確建置。

啟用 .NET MAUI 支援

若要使用衍生自 Element .NET for Android 的 .NET、適用於 iOS 的 .NET、適用於 Mac Catalyst 的 .NET for Mac Catalyst 或 WinUI 應用程式,您應該將原生應用程式專案新增至與您先前建立之 .NET MAUI 類別庫專案相同的解決方案。 然後,您應該在原生應用程式的項目檔中啟用 .NET MAUI 支援,方法是在項目檔的第一個<PropertyGroup>節點中,將 和 $(MauiEnablePlatformUsings) 建置屬性設定$(UseMaui)true

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

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

針對適用於 Mac Catalyst 應用程式的 .NET,您也必須將 build 屬性設定 $(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.Controls.Compatiblity NuGet 套件的項目檔Microsoft.Maui.Controls

<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上, OnCreate 類別中的 MainActivity 覆寫通常是執行應用程式啟動相關工作的位置。 下列程式代碼範例顯示類別中正在初始化的 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 上, 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;
        }
    }
}

然後,您可以在主要檢視控制器的 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 使用延遲初始化來建立 物件。 擴充 UseMauiEmbedding 方法會在物件上 MauiAppBuilder 叫用。 因此,您的原生應用程式項目應該包含您建立的 .NET MAUI 類別庫項目的參考,其中包含這個擴充方法。 MauiContext然後,會從 MauiApp 物件建立 物件,並bool決定內容的範圍。 將 MauiContext .NET MAUI 控件轉換成原生類型時,將會使用 物件。

取用 .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);
nativeView.WidthAnchor.ConstraintEqualTo(View.Frame.Width).Active = true;
nativeView.HeightAnchor.ConstraintEqualTo(500).Active = true;

在此範例中, ContentView衍生物件會轉換成 UIView 物件,然後設定其寬度和高度條件約束以允許互動。

注意

擴充 ToPlatformEmbedded 方法位於您稍早建立的 .NET MAUI 類別庫中。 因此,您的原生應用程式項目應該包含該專案的參考。

UIView然後,物件可以新增至檢視控制器中的檢視:

stackView.AddArrangedSubView(nativeView);

此外, ToUIViewController .NET MAUI 中的擴充方法可用來嘗試將 .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 熱重載。