Ampliación de la aplicación de escritorio con componentes de UWP modernos

Algunas experiencias de Windows (por ejemplo, una página de interfaz de usuario habilitada para entrada táctil) deben ejecutarse dentro de una instancia de AppContainer. Si desea agregar estas experiencias, extienda la aplicación de escritorio con proyectos de UWP y componentes de Windows Runtime.

En muchos casos, puede llamar a las API de Windows Runtime directamente desde la aplicación de escritorio, por lo que, antes de revisar esta guía, consulte Mejora para Windows.

Nota

Las características descritas en este tema requieren que la aplicación esté empaquetada (tenga identidad de paquete en tiempo de ejecución). Esto incluye aplicaciones empaquetadas (consulte Creación de un nuevo proyecto para una aplicación de escritorio de WinUI 3 empaquetada) y aplicaciones empaquetadas con ubicación externa (consulte Concesión de identidad del paquete mediante el empaquetado con ubicación externa). Consulte Características que requieren la identidad del paquete.

En primer lugar, configura la solución

Agrega uno o varios proyectos de UWP y componentes de Runtime a la solución.

Comienza con una solución que contenga un Proyecto de paquete de aplicación de Windows con una referencia a la aplicación de escritorio.

Esta imagen muestra un ejemplo de solución.

Extend start project

Si la solución no contiene un proyecto de empaquetado, consulta Empaquetado de la aplicación de escritorio mediante Visual Studio.

Configuración de la aplicación de escritorio

Asegúrate de que la aplicación de escritorio tiene referencias a los archivos que necesitas para llamar a las API de Windows Runtime.

Para ello, consulte Llamada a las API de Windows Runtime en aplicaciones de escritorio.

Agregar un proyecto de UWP

Agrega una Aplicación vacía (Windows universal) a la solución.

Aquí es donde debes crear una interfaz de usuario XAML moderna o usar las API que se ejecutan solo dentro de un proceso de UWP.

Add new project

En tu proyecto de empaquetado, haz clic con el botón derecho en el nodo Aplicaciones y, a continuación, elige Agregar referencia.

Add reference

A continuación, agrega una referencia al proyecto de UWP.

Select UWP project

Tu solución debe tener un aspecto similar a este:

Solution with UWP project

(Opcional) Agregar un componente de Windows Runtime

Para realizar algunos escenarios, tendrás que agregar código a un componente de Windows Runtime.

runtime component app service

A continuación, desde el proyecto de UWP, agrega una referencia al componente de Runtime. Tu solución debe tener un aspecto similar a este:

Runtime Component Reference

Compilar la solución

Compila la solución para asegurarte de que no aparezcan errores. Si aparecen errores, abre Configuration Manager y asegúrate de que los proyectos tengan como destino la misma plataforma.

Config manager

Echemos un vistazo a algunas cosas que puedes hacer con los proyectos de UWP y los componentes de Runtime.

Mostrar una interfaz de usuario XAML moderna

Como parte del flujo de la aplicación, puedes incorporar interfaces de usuario basadas en XAML modernas en la aplicación de escritorio. Estas interfaces de usuario pueden ser adaptables naturalmente para diferentes tamaños de pantalla y resoluciones, y admitir modelos interactivos modernos, como la entrada táctil y manuscrita.

Por ejemplo, con una pequeña cantidad de marcado XAML, puedes ofrecer a los usuarios potentes características de visualización relacionadas con mapas.

Esta imagen muestra una aplicación de Windows Forms que abre una interfaz de usuario moderna basada en XAML que contiene un control de mapa.

adaptive-design

Nota:

En este ejemplo se muestra una interfaz de usuario XAML mediante la adición de un proyecto de UWP a la solución. Este es el enfoque compatible estable para mostrar las interfaces de usuario XAML en una aplicación de escritorio. La alternativa a este enfoque consiste en agregar controles XAML de UWP directamente a la aplicación de escritorio mediante una isla XAML. Las islas XAML están disponibles actualmente como versión preliminar para desarrolladores. Aunque te animamos a probarlas ahora en tu propio código de prototipo, no recomendamos usarlos en el código de producción en este momento. Estas API y controles seguirán evolucionando y se estabilizarán en futuras versiones de Windows. Para obtener más información sobre las islas XAML, consulta Controles de UWP en aplicaciones de escritorio.

El modelo de diseño

Para mostrar una interfaz de usuario basada en XAML, deberás hacer lo siguiente:

1️⃣ Configurar la solución

2️⃣ Crear una interfaz de usuario XAML

3️⃣ Agregar una extensión de protocolo al proyecto de UWP

4️⃣ Iniciar la aplicación para UWP desde la aplicación de escritorio

5️⃣ En el proyecto de UWP, mostrar la página deseada

Configuración de la solución

Para obtener instrucciones generales sobre cómo configurar una solución, consulta la sección En primer lugar, configurar la solución al principio de esta guía.

La solución tendrá un aspecto similar a este:

XAML UI Solution

En este ejemplo, el proyecto de Windows Forms se denomina Landmarks y el proyecto de UWP que contiene la interfaz de usuario XAML, MapUI.

Crear una interfaz de usuario XAML

Agrega una interfaz de usuario XAML al proyecto de UWP. Este es el XAML para un mapa básico.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="12,20,12,14">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <maps:MapControl x:Name="myMap" Grid.Column="0" Width="500" Height="500"
                     ZoomLevel="{Binding ElementName=zoomSlider,Path=Value, Mode=TwoWay}"
                     Heading="{Binding ElementName=headingSlider,Path=Value, Mode=TwoWay}"
                     DesiredPitch="{Binding ElementName=desiredPitchSlider,Path=Value, Mode=TwoWay}"
                     HorizontalAlignment="Left"
                     MapServiceToken="<Your Key Goes Here" />
    <Grid Grid.Column="1" Margin="12">
        <StackPanel>
            <Slider Minimum="1" Maximum="20" Header="ZoomLevel" Name="zoomSlider" Value="17.5"/>
            <Slider Minimum="0" Maximum="360" Header="Heading" Name="headingSlider" Value="0"/>
            <Slider Minimum="0" Maximum="64" Header=" DesiredPitch" Name="desiredPitchSlider" Value="32"/>
        </StackPanel>
    </Grid>
</Grid>

Agregar una extensión de protocolo

En el Explorador de soluciones, abre el archivo package.appxmanifest del proyecto de empaquetado en tu solución y agrega esta extensión.

<Extensions>
  <uap:Extension Category="windows.protocol" Executable="MapUI.exe" EntryPoint="MapUI.App">
    <uap:Protocol Name="xamluidemo" />
  </uap:Extension>
</Extensions>

Asigna un nombre al protocolo, proporciona el nombre del archivo ejecutable generado por el proyecto de UWP y el nombre de la clase del punto de entrada.

También puedes abrir el archivo package.appxmanifest en el diseñador, elegir la pestaña Declaraciones y, a continuación, agregar la extensión allí.

declarations-tab

Nota:

Los controles de mapa descargan datos de Internet, por lo que si usas uno, tendrás que agregar la funcionalidad de "cliente de Internet" al manifiesto también.

Iniciar la aplicación para UWP

En primer lugar, desde la aplicación de escritorio, crea un Uri que incluya el nombre del protocolo y los parámetros que quieras pasar a la aplicación para UWP. Luego llama al método LaunchUriAsync.


private void Statue_Of_Liberty_Click(object sender, EventArgs e)
{
    ShowMap(40.689247, -74.044502);
}

private async void ShowMap(double lat, double lon)
{
    string str = "xamluidemo://";

    Uri uri = new Uri(str + "location?lat=" +
        lat.ToString() + "&?lon=" + lon.ToString());

    var success = await Windows.System.Launcher.LaunchUriAsync(uri);

}

Analizar parámetros y mostrar una página

En la clase Aplicación del proyecto de UWP, invalida el controlador de eventos OnActivated. Si el protocolo activa la aplicación, analiza los parámetros y luego abre la página que quieras.

protected override void OnActivated(Windows.ApplicationModel.Activation.IActivatedEventArgs e)
{
    if (e.Kind == ActivationKind.Protocol)
    {
        ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)e;
        Uri uri = protocolArgs.Uri;
        if (uri.Scheme == "xamluidemo")
        {
            Frame rootFrame = new Frame();
            Window.Current.Content = rootFrame;
            rootFrame.Navigate(typeof(MainPage), uri.Query);
            Window.Current.Activate();
        }
    }
}

En el código subyacente de la página XAML, invalida el método OnNavigatedTo para usar los parámetros pasados a la página. En este caso, usaremos la latitud y la longitud que se pasaron a esta página para mostrar una ubicación en un mapa.

protected override void OnNavigatedTo(NavigationEventArgs e)
 {
     if (e.Parameter != null)
     {
         WwwFormUrlDecoder decoder = new WwwFormUrlDecoder(e.Parameter.ToString());

         double lat = Convert.ToDouble(decoder[0].Value);
         double lon = Convert.ToDouble(decoder[1].Value);

         BasicGeoposition pos = new BasicGeoposition();

         pos.Latitude = lat;
         pos.Longitude = lon;

         myMap.Center = new Geopoint(pos);

         myMap.Style = MapStyle.Aerial3D;

     }

     base.OnNavigatedTo(e);
 }

Convertir la aplicación de escritorio en un destino de recursos compartidos

Puedes convertir la aplicación de escritorio en un destino de recursos compartidos para que los usuarios puedan compartir con facilidad datos, como imágenes de otras aplicaciones que admiten el uso compartido.

Por ejemplo, los usuarios podrían elegir tu aplicación para compartir imágenes desde Microsoft Edge, la aplicación Fotos. Esta es una aplicación de ejemplo WPF que tiene esa funcionalidad.

share target.

Consulta el ejemplo completo aquí.

El modelo de diseño

Para que la aplicación sea un destino de recursos compartidos, deberás hacer lo siguiente:

1️⃣ Agregar una extensión de destino de recursos compartidos

2️⃣ Invalidar el controlador de eventos OnShareTargetActivated

3️⃣ Agregar extensiones de escritorio al proyecto de UWP

4️⃣ Agregar la extensión de proceso de plena confianza

5️⃣ Modificar la aplicación de escritorio para obtener el archivo compartido

Los pasos siguientes

Agregar una extensión de destino de recursos compartidos

En el Explorador de soluciones, abre el archivo package.appxmanifest del proyecto de empaquetado en tu solución y agrega la extensión de destino de recursos compartidos.

<Extensions>
      <uap:Extension
          Category="windows.shareTarget"
          Executable="ShareTarget.exe"
          EntryPoint="App">
        <uap:ShareTarget>
          <uap:SupportedFileTypes>
            <uap:SupportsAnyFileType />
          </uap:SupportedFileTypes>
          <uap:DataFormat>Bitmap</uap:DataFormat>
        </uap:ShareTarget>
      </uap:Extension>
</Extensions>  

Proporciona el nombre del archivo ejecutable generado por el proyecto de UWP y el nombre de la clase del punto de entrada. Este marcado supone que el nombre del archivo ejecutable de la aplicación para UWP es ShareTarget.exe.

También tendrás que especificar qué tipos de archivos se pueden compartir con la aplicación. En este ejemplo, para convertir la aplicación de escritorio WPF PhotoStoreDemo en un destino de recursos compartidos para las imágenes de mapa de bits, se especifica Bitmap para el tipo de archivo compatible.

Invalidar el controlador de eventos OnShareTargetActivated

Invalida el controlador de eventos OnShareTargetActivated en la clase App del proyecto de UWP.

Se llama a este controlador de eventos cuando los usuarios eligen tu aplicación para compartir sus archivos.


protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
    shareWithDesktopApplication(args.ShareOperation);
}

private async void shareWithDesktopApplication(ShareOperation shareOperation)
{
    if (shareOperation.Data.Contains(StandardDataFormats.StorageItems))
    {
        var items = await shareOperation.Data.GetStorageItemsAsync();
        StorageFile file = items[0] as StorageFile;
        IRandomAccessStreamWithContentType stream = await file.OpenReadAsync();

        await file.CopyAsync(ApplicationData.Current.LocalFolder);
            shareOperation.ReportCompleted();

        await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
    }
}

En este código, se guarda la imagen que el usuario comparte en una carpeta de almacenamiento local de aplicaciones. Más adelante, modificaremos la aplicación de escritorio para extraer imágenes de esa misma carpeta. La aplicación de escritorio puede hacerlo porque está incluida en el mismo paquete que la aplicación para UWP.

Agregar extensiones de escritorio al proyecto de UWP

Agrega la extensión Windows Desktop Extensions for the UWP al proyecto de la aplicación para UWP. Verá más de una versión de la extensión (por ejemplo, 10.0.18362.0 y 10.0.19041.0). Para obtener información sobre cómo elegir una versión, consulte SDK de extensión y cómo hacer referencia a ellos.

desktop extension

Agregar la extensión de proceso de plena confianza

En el Explorador de soluciones, abre el archivo package.appxmanifest del proyecto de empaquetado en la solución y, a continuación, agrega la extensión de proceso de plena confianza junto a la extensión de destino de recursos compartidos que agregaste anteriormente a este archivo.

<Extensions>
  ...
      <desktop:Extension Category="windows.fullTrustProcess" Executable="PhotoStoreDemo\PhotoStoreDemo.exe" />
  ...
</Extensions>  

Esta extensión permitirá a la aplicación para UWP iniciar la aplicación de escritorio en la que quieres compartir un archivo. En el ejemplo, hacemos referencia al archivo ejecutable de la aplicación de escritorio WPF PhotoStoreDemo.

Modificar la aplicación de escritorio para obtener el archivo compartido

Modifica la aplicación de escritorio para buscar y procesar el archivo compartido. En este ejemplo, la aplicación para UWP almacenó el archivo compartido en la carpeta de datos de la aplicación local. Por lo tanto, se modificará la aplicación de escritorio WPF PhotoStoreDemo para extraer fotos de esa carpeta.

Photos.Path = Windows.Storage.ApplicationData.Current.LocalFolder.Path;

En el caso de las instancias de la aplicación de escritorio que ya ha abierto el usuario, también se puede controlar el evento FileSystemWatcher y pasar la ruta de acceso a la ubicación del archivo. De este modo, las instancias abiertas de la aplicación de escritorio mostrarán la foto compartida.

...

   FileSystemWatcher watcher = new FileSystemWatcher(Photos.Path);

...

private void Watcher_Created(object sender, FileSystemEventArgs e)
{
    // new file got created, adding it to the list
    Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
    {
        if (File.Exists(e.FullPath))
        {
            ImageFile item = new ImageFile(e.FullPath);
            Photos.Insert(0, item);
            PhotoListBox.SelectedIndex = 0;
            CurrentPhoto.Source = (BitmapSource)item.Image;
        }
    }));
}

Crear una tarea en segundo plano

Se debe añadir una tarea en segundo plano para ejecutar código incluso cuando la aplicación está suspendida. Las tareas en segundo plano son geniales para tareas pequeñas que no requieren la interacción del usuario. Por ejemplo, la tarea puede descargar correo, mostrar una notificación del sistema sobre un mensaje de chat entrante o reaccionar a un cambio en una condición del sistema.

A continuación se incluye un ejemplo de aplicación WPF que registra una tarea en segundo plano.

background task

La tarea realiza una solicitud HTTP y mide el tiempo que tarda la solicitud en devolver una respuesta. Tus tareas probablemente serán mucho más interesantes, pero este ejemplo es ideal para conocer la mecánica básica de una tarea en segundo plano.

Consulta el ejemplo completo aquí.

El modelo de diseño

Para crear un servicio en segundo plano, deberás hacer lo siguiente:

1️⃣ Implementar la tarea en segundo plano

2️⃣ Configurar la tarea en segundo plano

3️⃣ Registrar la tarea en segundo plano

Implementar la tarea en segundo plano

Para implementar la tarea en segundo plano, agrega el código a un proyecto de componente de Windows Runtime.

public sealed class SiteVerifier : IBackgroundTask
{
    public async void Run(IBackgroundTaskInstance taskInstance)
    {

        taskInstance.Canceled += TaskInstance_Canceled;
        BackgroundTaskDeferral deferral = taskInstance.GetDeferral();
        var msg = await MeasureRequestTime();
        ShowToast(msg);
        deferral.Complete();
    }

    private async Task<string> MeasureRequestTime()
    {
        string msg;
        try
        {
            var url = ApplicationData.Current.LocalSettings.Values["UrlToVerify"] as string;
            var http = new HttpClient();
            Stopwatch clock = Stopwatch.StartNew();
            var response = await http.GetAsync(new Uri(url));
            response.EnsureSuccessStatusCode();
            var elapsed = clock.ElapsedMilliseconds;
            clock.Stop();
            msg = $"{url} took {elapsed.ToString()} ms";
        }
        catch (Exception ex)
        {
            msg = ex.Message;
        }
        return msg;
    }

Configurar la tarea en segundo plano

En el diseñador de manifiesto, abre el archivo package.appxmanifest del proyecto de empaquetado en tu solución.

En la pestaña Declaraciones, agrega una declaración de Tareas en segundo plano.

Background task option

A continuación, elige las propiedades deseadas. En nuestro ejemplo se usa la propiedad Timer.

Timer property

Proporciona el nombre completo de la clase en el componente de Windows Runtime que implementa la tarea en segundo plano.

Specify entry point

Registrar la tarea en segundo plano

Agrega código al proyecto de aplicación de escritorio que registre la tarea en segundo plano.

public void RegisterBackgroundTask(String triggerName)
{
    var current = BackgroundTaskRegistration.AllTasks
        .Where(b => b.Value.Name == triggerName).FirstOrDefault().Value;

    if (current is null)
    {
        BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
        builder.Name = triggerName;
        builder.SetTrigger(new MaintenanceTrigger(15, false));
        builder.TaskEntryPoint = "HttpPing.SiteVerifier";
        builder.Register();
        System.Diagnostics.Debug.WriteLine("BGTask registered:" + triggerName);
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("Task already:" + triggerName);
    }
}

Encontrar respuestas a tus preguntas

¿Tienes alguna pregunta? Pregúntanos en Stack Overflow. Nuestro equipo supervisa estas etiquetas. También puedes preguntarnos aquí.