Creación de una aplicación WinUI de instancia única con C#

En este procedimiento se muestra cómo crear una aplicación WinUI 3 con instancia única con C# y el SDK de Aplicaciones para Windows. Las aplicaciones de instancia única solo permiten una instancia de la aplicación que se ejecuta a la vez. Las aplicaciones De WinUI se realizan con instancias múltiples de forma predeterminada. Permiten iniciar varias instancias de la misma aplicación simultáneamente. Esto se hace referencia a varias instancias. Sin embargo, es posible que quiera implementar la creación de instancias únicas en función del caso de uso de la aplicación. Si intenta iniciar una segunda instancia de una aplicación de instancia única, solo se activará la ventana principal de la primera instancia. En este tutorial se muestra cómo implementar instancias únicas en una aplicación WinUI.

En este artículo, aprenderá a:

  • Desactivar el código generado Program de XAML
  • Definición del método personalizado Main para el redireccionamiento
  • Prueba de la creación de instancias únicas después de la implementación de la aplicación

Requisitos previos

En este tutorial se usa Visual Studio y se basa en la plantilla de aplicación en blanco de WinUI. Si no está familiarizado con el desarrollo de WinUI, puede configurar siguiendo las instrucciones de Introducción a WinUI. Allí instalará Visual Studio, lo configurará para desarrollar aplicaciones con WinUI, a la vez que se garantiza que tiene la versión más reciente de WinUI y el SDK de Aplicaciones para Windows y crea un proyecto de Hola mundo.

Cuando haya terminado, vuelva aquí para aprender a convertir el proyecto de "Hola mundo" en una aplicación de instancia única.

Nota

Este procedimiento se basa en la entrada de blog Make the app single-instanced (Part 3) de una serie de blog de Windows en WinUI 3. El código de esos artículos está disponible en GitHub.

Deshabilitación del código del programa generado automáticamente

Es necesario comprobar la redirección tan pronto como sea posible, antes de crear cualquier ventana. Para ello, debemos definir el símbolo "DISABLE_XAML_GENERATED_MAIN" en el archivo del proyecto. Siga estos pasos para deshabilitar el código del programa generado automáticamente:

  1. Haga clic con el botón derecho en el nombre del proyecto en Explorador de soluciones y seleccione Editar archivo de proyecto.

  2. Defina el símbolo DISABLE_XAML_GENERATED_MAIN para cada configuración y plataforma. Agregue el siguiente CÓDIGO XML al archivo del proyecto:

    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
      <DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
      <DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
      <DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
      <DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|arm64'">
      <DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|arm64'">
      <DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
    </PropertyGroup>
    

Al agregar el símbolo DISABLE_XAML_GENERATED_MAIN se deshabilitará el código del programa generado automáticamente para el proyecto.

Definir una clase Program con un método Main

Se debe crear un archivo Program.cs personalizado en lugar de ejecutar el método Main predeterminado. El código agregado a la clase Program permite a la aplicación comprobar la redirección, que no es el comportamiento predeterminado de las aplicaciones WinUI.

  1. Vaya a Explorador de soluciones, haga clic con el botón derecho en el nombre del proyecto y seleccione Agregar | Clase.

  2. Asigne un nombre a la nueva clase Program.cs y seleccione Agregar.

  3. Agregue los siguientes espacios de nombres a la clase Program, reemplazando los espacios de nombres existentes:

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.UI.Dispatching;
    using Microsoft.UI.Xaml;
    using Microsoft.Windows.AppLifecycle;
    
  4. Reemplace la clase Program vacía por lo siguiente:

    public class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            WinRT.ComWrappersSupport.InitializeComWrappers();
            bool isRedirect = DecideRedirection();
    
            if (!isRedirect)
            {
                Application.Start((p) =>
                {
                    var context = new DispatcherQueueSynchronizationContext(
                        DispatcherQueue.GetForCurrentThread());
                    SynchronizationContext.SetSynchronizationContext(context);
                    _ = new App();
                });
            }
    
            return 0;
        }
    }
    

    El método Main determina si la aplicación debe redirigir a la primera instancia o iniciar una nueva instancia después de llamar a DecideRedirection, que definiremos a continuación.

  5. Defina el método DecideRedirection debajo del método Main :

    private static bool DecideRedirection()
    {
        bool isRedirect = false;
        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind kind = args.Kind;
        AppInstance keyInstance = AppInstance.FindOrRegisterForKey("MySingleInstanceApp");
    
        if (keyInstance.IsCurrent)
        {
            keyInstance.Activated += OnActivated;
        }
        else
        {
            isRedirect = true;
            RedirectActivationTo(args, keyInstance);
        }
    
        return isRedirect;
    }
    

    DecideRedirection determina si la aplicación se ha registrado registrando una clave única que representa la instancia de la aplicación. En función del resultado del registro de claves, puede determinar si hay una instancia actual de la aplicación en ejecución. Después de realizar la determinación, el método sabe si redirigir o permitir que la aplicación continúe iniciando la nueva instancia. Se llama al método RedirectActivationTo si es necesario el redireccionamiento.

  6. A continuación, vamos a crear el método RedirectActivationTo debajo del método DecideRedirection, junto con las instrucciones DllImport necesarias. Agregue el código siguiente a la clase Program:

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(
        IntPtr lpEventAttributes, bool bManualReset,
        bool bInitialState, string lpName);
    
    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);
    
    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(
        uint dwFlags, uint dwMilliseconds, ulong nHandles,
        IntPtr[] pHandles, out uint dwIndex);
    
    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);
    
    private static IntPtr redirectEventHandle = IntPtr.Zero;
    
    // Do the redirection on another thread, and use a non-blocking
    // wait method to wait for the redirection to complete.
    public static void RedirectActivationTo(AppActivationArguments args,
                                            AppInstance keyInstance)
    {
        redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });
    
        uint CWMO_DEFAULT = 0;
        uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(
           CWMO_DEFAULT, INFINITE, 1,
           [redirectEventHandle], out uint handleIndex);
    
        // Bring the window to the foreground
        Process process = Process.GetProcessById((int)keyInstance.ProcessId);
        SetForegroundWindow(process.MainWindowHandle);
    }
    

    El método RedirectActivationTo es responsable de redirigir la activación a la primera instancia de la aplicación. Crea un identificador de eventos, inicia un nuevo subproceso para redirigir la activación y espera a que se complete la redirección. Una vez completada la redirección, el método lleva la ventana al primer plano.

  7. Por último, defina el método auxiliar OnActivated debajo del método DecideRedirection :

    private static void OnActivated(object sender, AppActivationArguments args)
    {
        ExtendedActivationKind kind = args.Kind;
    }
    

Prueba de la creación de instancias únicas mediante la implementación de aplicaciones

Hasta este punto, hemos estado probando la aplicación mediante la depuración en Visual Studio. Sin embargo, solo podemos tener un depurador ejecutándose a la vez. Esta limitación nos impide saber si la aplicación tiene una instancia única porque no podemos depurar el mismo proyecto dos veces al mismo tiempo. Para una prueba precisa, implementaremos la aplicación en nuestro cliente local de Windows. Después de la implementación, podemos iniciar la aplicación desde el escritorio como lo haría con cualquier aplicación instalada en Windows.

  1. Vaya a Explorador de soluciones, haga clic con el botón derecho en el nombre del proyecto y seleccione Implementar.

  2. Abra el menú Inicio y haga clic en el campo de búsqueda.

  3. Escriba el nombre de la aplicación en el campo de búsqueda.

  4. Haga clic en el icono de la aplicación en el resultado de búsqueda para iniciar la aplicación.

    Nota

    Si experimenta bloqueos en la aplicación en modo de versión, hay algunos problemas conocidos con las aplicaciones recortadas en el SDK de Aplicaciones para Windows. Puede deshabilitar el recorte en el proyecto estableciendo la propiedad PublishTrimmed en false para todas las configuraciones de compilación de los archivos del .pubxml proyecto. Para obtener más información, consulte este problema en GitHub.

  5. Repita los pasos del 2 al 4 para volver a iniciar la misma aplicación y vea si se abre otra instancia. Si la aplicación tiene una instancia única, se activará la primera instancia en lugar de una nueva apertura de instancia.

    Sugerencia

    Opcionalmente, puede agregar código de registro al método OnActivated para comprobar que se ha activado la instancia existente. Intente pedir ayuda a Copilot para agregar una implementación de ILogger a la aplicación WinUI.

Resumen

Todo el código que se trata aquí está en GitHub, con ramas para los distintos pasos de la serie de blog de Windows original. Consulte la rama de creación de instancias únicas para ver el código específico de este procedimiento. La main rama es la más completa. Las otras ramas están diseñadas para mostrar cómo evolucionó la arquitectura de la aplicación.

Creación de instancias de aplicaciones con la API del ciclo de vida de la aplicación

Creación de una instancia única de la aplicación (parte 3)

Ejemplo de WinAppSDK-DrumPad en GitHub