Compartilhar via


Criar um aplicativo WinUI de instância única com C#

Este tutorial demonstra como criar um aplicativo WinUI 3 de instância única com C# e o SDK do Aplicativo Windows. Os aplicativos de instância única permitem apenas uma instância do aplicativo em execução por vez. Os aplicativos WinUI são de várias instâncias por padrão. Eles permitem que você inicie várias instâncias do mesmo aplicativo simultaneamente. Isso se refere a várias instâncias. No entanto, talvez você queira implementar a instância única com base no caso de uso do seu aplicativo. A tentativa de iniciar uma segunda instância de um aplicativo de instância única resultará apenas na ativação da janela principal da primeira instância. Este tutorial demonstra como implementar a instanciação única em um aplicativo WinUI.

Neste artigo, você aprenderá a:

  • Desativar o código gerado Program pelo XAML
  • Definir método personalizado Main para redirecionamento
  • Testar a instanciação única após a implantação do aplicativo

Pré-requisitos

Este tutorial usa o Visual Studio e se baseia no modelo de aplicativo em branco do WinUI. Se você não estiver familiarizado com o desenvolvimento do WinUI, poderá configurar seguindo as instruções em Introdução ao WinUI. Lá, você instalará o Visual Studio, configurá-lo para desenvolver aplicativos com o WinUI, garantindo que você tenha a versão mais recente do WinUI e do SDK do Aplicativo Windows e criará um projeto Hello World.

Quando você tiver feito isso, volte aqui para saber como transformar seu projeto "Hello World" em um aplicativo de instância única.

Observação

Este tutorial se baseia na postagem de blog Tornando o aplicativo de instância única (Parte 3) de uma série de blogs do Windows no WinUI 3. O código para esses artigos está disponível no GitHub.

Desativar código de programa gerado automaticamente

Precisamos verificar o redirecionamento o mais cedo possível, antes de criar qualquer janela. Para fazer isso, devemos definir o símbolo "DISABLE_XAML_GENERATED_MAIN" no arquivo do projeto. Siga estas etapas para desativar o código do programa gerado automaticamente:

  1. Clique com o botão direito do mouse no nome do projeto no Gerenciador de Soluções e selecione Editar Arquivo de Projeto.

  2. Defina o símbolo DISABLE_XAML_GENERATED_MAIN para cada configuração e plataforma. Adicione o seguinte XML ao arquivo do projeto:

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

Adicionar o símbolo de DISABLE_XAML_GENERATED_MAIN desativará o código do programa gerado automaticamente para o seu projeto.

Definir uma classe Program com um método Main

Um arquivo Program.cs personalizado deve ser criado em vez de executar o método Main padrão. O código adicionado à classe Program permite que o aplicativo verifique se há redirecionamento, que não é o comportamento padrão dos aplicativos WinUI.

  1. Navegue até Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto e selecione Adicionar | Classe.

  2. Nomeie a nova classe Program.cs e selecione Adicionar.

  3. Adicione os seguintes namespaces à classe Program, substituindo os namespaces 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. Substitua a classe Program vazia pelo seguinte:

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

    O método Main determina se o aplicativo deve redirecionar para a primeira instância ou iniciar uma nova instância depois de chamar DecideRedirection, que definiremos a seguir.

  5. Defina o método DecideRedirection abaixo do 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 se o aplicativo foi registrado registrando uma chave exclusiva que representa sua instância do aplicativo. Com base no resultado do registro de chave, ele pode determinar se há uma instância atual do aplicativo em execução. Depois de fazer a determinação, o método sabe se deve redirecionar ou permitir que o aplicativo continue iniciando a nova instância. O método RedirectActivationTo será chamado se o redirecionamento for necessário.

  6. Em seguida, vamos criar o método RedirectActivationTo abaixo do método DecideRedirection, juntamente com as instruções DllImport necessárias. Adicione o seguinte código à classe 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);
    }
    

    O método RedirectActivationTo é responsável por redirecionar a ativação para a primeira instância do aplicativo. Ele cria um identificador de evento, inicia um novo thread para redirecionar a ativação e aguarda a conclusão do redirecionamento. Após a conclusão do redirecionamento, o método traz a janela para o primeiro plano.

  7. Por fim, defina o método auxiliar OnActivated abaixo do método DecideRedirection :

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

Testar a instanciação única por meio da implantação do aplicativo

Até este ponto, estamos testando o aplicativo depurando no Visual Studio. No entanto, só podemos ter um depurador em execução por vez. Essa limitação nos impede de saber se o aplicativo é de instância única porque não podemos depurar o mesmo projeto duas vezes ao mesmo tempo. Para um teste preciso, implantaremos o aplicativo em nosso cliente Windows local. Após a implantação, podemos iniciar o aplicativo na área de trabalho como você faria com qualquer aplicativo instalado no Windows.

  1. Navegue até Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto e selecione Implantar.

  2. Abra o menu Iniciar e clique no campo de pesquisa.

  3. Digite o nome do seu aplicativo no campo de pesquisa.

  4. Clique no ícone do aplicativo no resultado da pesquisa para iniciar seu aplicativo.

    Observação

    Se você tiver falhas de aplicativo no modo de versão, há alguns problemas conhecidos com aplicativos cortados no SDK do Aplicativo Windows. Você pode desabilitar o corte no projeto definindo a propriedade PublishTrimmed como false para todas as configurações de build nos arquivos do .pubxml projeto. Para obter mais informações, consulte este problema no GitHub.

  5. Repita as etapas 2 a 4 para iniciar o mesmo aplicativo novamente e ver se outra instância é aberta. Se o aplicativo for de instância única, a primeira instância será ativada em vez de uma nova instância ser aberta.

    Dica

    Opcionalmente, você pode adicionar algum código de log ao método OnActivated para verificar se a instância existente foi ativada. Tente pedir ajuda ao Copilot para adicionar uma implementação ILogger ao seu aplicativo WinUI.

Resumo

Todo o código abordado aqui está no GitHub, com ramificações para as diferentes etapas da série de blog original do Windows. Consulte o branch de instância única para obter o código específico para este tutorial. O main ramo é o mais abrangente. As outras ramificações destinam-se a mostrar como a arquitetura do aplicativo evoluiu.

Instanciação de aplicativo com a API de ciclo de vida do aplicativo

Tornando o aplicativo de instância única (Parte 3)

Exemplo de WinAppSDK-DrumPad no GitHub