Share via


Inserción nativa

Examinar ejemplo. Examinar el ejemplo

Normalmente, una aplicación de.NET Multi-platform App UI (.NET MAUI) incluye páginas que contienen diseños, como Grid, y diseños que contienen vistas, como Button. Las páginas, los diseños y las vistas se derivan de la clase Element. La inserción nativa permite que cualquier control de .NET MAUI que derive de Element se consuma en aplicaciones nativas de .NET para Android, .NET para iOS, .NET para Mac Catalyst y WinUI.

El proceso para consumir un control .NET MAUI en una aplicación nativa es el siguiente:

  1. Cree métodos de extensión para arrancar la aplicación insertada nativa. Para obtener más información, consulte Crear métodos de extensión.
  2. Cree un proyecto único de .NET MAUI que contenga la interfaz de usuario de .NET MAUI y las dependencias. Para obtener más información, consulte Creación de un proyecto único de .NET MAUI.
  3. Cree una aplicación nativa y habilite la compatibilidad con .NET MAUI en ella. Para obtener más información, consulte Habilitación de la compatibilidad con .NET MAUI.
  4. Inicialice .NET MAUI llamando al método de extensión UseMauiEmbedding. Para obtener más información, consulte Initialize .NET MAUI(Inicializar .NET MAUI).
  5. Cree la interfaz de usuario de MAUI de .NET y conviértala en el tipo nativo adecuado con el método de extensión ToPlatformEmbedding. Para obtener más información, consulte Consumo de controles MAUI de .NET.

Nota:

Al usar la inserción nativa, el motor de enlace de datos de .NET MAUI sigue funcionando. Sin embargo, la navegación de páginas debe realizarse mediante la API de navegación nativa.

Creación de métodos de extensión

Antes de crear una aplicación nativa que consuma controles MAUI de .NET, primero debe crear un proyecto de biblioteca de clases .NET MAUI y eliminar la carpeta Platforms y la clase Class1 de ella. A continuación, agregue una clase a ella denominada EmbeddedExtensions que contenga el código siguiente:

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

Estos métodos de extensión están en el espacio de nombres Microsoft.Maui.Controls y se usan para arrancar la aplicación insertada nativa en cada plataforma. Los métodos de extensión hacen referencia a los tipos EmbeddedPlatformApplication, EmbeddedWindowHandler, y EmbeddedWindowProvider que también debe agregar al proyecto de biblioteca MAUI de .NET.

El código siguiente muestra la clase EmbeddedPlatformApplication, que se debe agregar al mismo proyecto de biblioteca MAUI de .NET que la clase 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);
    }
}

El código siguiente muestra la clase EmbeddedWindowHandler, que se debe agregar al mismo proyecto de biblioteca MAUI de .NET que la clase 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.");
}

El código siguiente muestra la clase EmbeddedWindowProvider, que se debe agregar al mismo proyecto de biblioteca MAUI de .NET que la clase 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;
}

Creación de un proyecto único de MAUI de .NET

Antes de crear una aplicación nativa que consuma controles MAUI de .NET, debe agregar un proyecto de aplicación MAUI de .NET a la misma solución que el proyecto de biblioteca de clases MAUI de .NET que ha creado anteriormente. El proyecto de aplicación MAUI de .NET almacenará la interfaz de usuario que quiere volver a usar en la aplicación insertada nativa. Después de agregar un nuevo proyecto de aplicación MAUI de .NET a la solución, realice los pasos siguientes:

  1. Elimine la carpeta Propiedades del proyecto.

  2. Elimine la carpeta Plataforma del proyecto.

  3. Elimine la carpeta Resources/AppIcon del proyecto.

  4. Elimine la carpeta Resources/raw del proyecto.

  5. Elimine la carpetaResources/Splash del proyecto.

  6. Elimine la clase AppShell del proyecto.

  7. Modifique la clase App para que no establezca la propiedad MainPage:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
    
  8. Elimine la clase MainPage del proyecto.

  9. Modifique el archivo de proyecto para que la propiedad de compilación $(TargetFrameworks) esté establecida en net8.0 y se quite la propiedad de compilación $(OutputType) :

    <PropertyGroup>
      <TargetFrameworks>net8.0</TargetFrameworks>
    
      <RootNamespace>MyMauiApp</RootNamespace>
      <UseMaui>true</UseMaui>
      <SingleProject>true</SingleProject>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
    
      ...
    </PropertyGroup>
    
  10. Modifique el método CreateMauiApp de la clase MauiProgram para que acepte un argumento opcional Action<MauiAppBuilder> que se invoca antes de que el método devuelva:

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

En este punto, debe agregar la interfaz de usuario de .NET MAUI necesaria al proyecto, incluidas las dependencias y los recursos, y asegurarse de que el proyecto se compila correctamente.

Habilitación de la compatibilidad con .NET MAUI

Para consumir controles de .NET MAUI que deriven de Element en una aplicación .NET para Android, .NET para iOS, .NET para Mac Catalyst o WinUI, deberá agregar el proyecto de su aplicación nativa a la misma solución que el proyecto de biblioteca de clases de .NET MAUI que creó anteriormente. A continuación, debe habilitar la compatibilidad con MAUI de .NET en el archivo de proyecto de la aplicación nativa estableciendo las propiedades de compilación $(UseMaui) y $(MauiEnablePlatformUsings) en true el primer nodo <PropertyGroup> del archivo de proyecto:

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

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

En el caso de las aplicaciones .NET para Mac Catalyst, también deberá establecer la propiedad de compilación $(SupportedOSPlatformVersion) en 14.0 como mínimo:

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

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

En el caso de las aplicaciones WinUI, también deberá establecer la propiedad de compilación $(EnableDefaultXamlItems) en false:

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

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

Esto dejará de recibir errores de compilación sobre el método InitializeComponent que ya se está definiendo.

A continuación, agregue elementos de compilación $(PackageReference) al archivo de proyecto para los paquetes NuGet Microsoft.Maui.Controls y Microsoft.Maui.Controls.Compatiblity:

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

Inicializar .NET MAUI

.NET MAUI debe inicializarse antes de que un proyecto de aplicación nativa pueda construir un control .NET MAUI. Elegir cuándo inicializarlo depende principalmente de cuándo es más conveniente en el flujo de la aplicación: se puede realizar en el inicio o justo antes de que se construya un control MAUI de .NET. El enfoque descrito aquí es inicializar .NET MAUI cuando se crea la interfaz de usuario inicial de la aplicación.

Normalmente, el patrón para inicializar .NET MAUI en un proyecto de aplicación nativa es el siguiente:

En Android, la invalidación OnCreate de la clase MainActivity suele ser el lugar para realizar tareas relacionadas con el inicio de la aplicación. En el ejemplo de código siguiente se muestra que se inicializa .NET MAUI en la clase 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

            ...              
        }
    }
}

En iOS y Mac Catalyst, la invalidación FinishedLaunching en la clase AppDelegate debe modificarse para crear el controlador de vista principal:

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

A continuación, se puede inicializar .NET MAUI en el método ViewDidLoad en el controlador de vista principal:

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

            ...
        }
    }
}

En Windows, la clase MainWindow suele ser el lugar para realizar tareas de inicio de aplicaciones relacionadas con la interfaz de usuario:

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

            ...
        }
    }
}

En este ejemplo, el objeto MauiApp se crea mediante la inicialización diferida. El método de extensión UseMauiEmbedding se invoca en el objeto MauiAppBuilder. Por lo tanto, el proyecto de aplicación nativa debe incluir una referencia al proyecto de biblioteca de clases MAUI de .NET que ha creado lo que contiene este método de extensión. A continuación, se crea un objeto MauiContext a partir del objeto MauiApp, con un bool determinando desde dónde se limita el contexto. El objeto MauiContext se usará al convertir controles .NET MAUI a tipos nativos.

Consumo de controles .NET MAUI

Después de inicializar .NET MAUI en la aplicación nativa, puede agregar la interfaz de usuario de .NET MAUI al diseño de la aplicación nativa. Esto se puede lograr mediante la creación de una instancia de la interfaz de usuario y la conversión al tipo nativo adecuado con el método de extensión ToPlatformEmbedded.

En Android, el método de extensión ToPlatformEmbedded convierte el control MAUI de .NET en un objeto android View:

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

En este ejemplo, un objeto derivado de ContentView se convierte en un objeto View de Android.

Nota:

El método de extensión ToPlatformEmbedded está en la biblioteca de clases MAUI de .NET que creó anteriormente. Por lo tanto, el proyecto de aplicación nativa debe incluir una referencia a ese proyecto.

A continuación, el objeto View se puede agregar a un diseño de la aplicación nativa:

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

En iOS y Mac Catalyst, el método de extensión ToPlatformEmbedded convierte el control .NET MAUI en un objeto UIView:

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

En este ejemplo, un objeto derivado de ContentView se convierte en un objeto UIView y después se le establecen restricciones de anchura y altura para permitir la interacción.

Nota:

El método de extensión ToPlatformEmbedded está en la biblioteca de clases MAUI de .NET que creó anteriormente. Por lo tanto, el proyecto de aplicación nativa debe incluir una referencia a ese proyecto.

A continuación, el objeto UIView se puede agregar a una vista en el controlador de vista:

stackView.AddArrangedSubView(nativeView);

Además, se puede usar un método de extensión ToUIViewController en .NET MAUI para intentar convertir una página MAUI de .NET en un UIViewController:

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

En este ejemplo, un objeto derivado de ContentPage se convierte en un objeto UIViewController.

En Windows, el método de extensión ToPlatformEmbedded convierte el control MAUI de .NET en un objeto FrameworkElement:

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

En este ejemplo, un objeto derivado de ContentView se convierte en un objeto FrameworkElement. Después, el objeto FrameworkElement se puede definir como el contenido de una página WinUI.

A continuación, el objeto FrameworkElement se puede agregar a un diseño de la aplicación nativa:

stackPanel.Children.Add(nativeView);

Importante

Para evitar un error, la recarga activa de XAML debe deshabilitarse antes de ejecutar una aplicación insertada nativa en la configuración de depuración.