Compartilhar via


Enviar uma notificação local app de uma área de trabalho do WRL C++ app

Aplicativos de área de trabalho empacotados e descompactados podem enviar notificações interativas app , assim como os aplicativos da Plataforma Universal do Windows (UWP). Isso inclui aplicativos empacotados (consulte Criar um novo projeto para uma área de trabalho appdo WinUI 3 empacotada); aplicativos empacotados com local externo (consulte Conceder identidade do pacote empacotando com localização externa); e aplicativos não empacotados (consulte Criar um novo projeto para uma área de trabalho appWinUI 3 não empacotada).

No entanto, para um computador não empacotado app, há algumas etapas especiais. Isso ocorre devido aos diferentes esquemas de ativação e à falta de identidade do pacote em runtime.

Note

O termo "toast notificação" está sendo substituído por "app notificação". Esses termos se referem ao mesmo recurso do Windows, mas, com o tempo, eliminaremos gradualmente o uso de "toast notificação" na documentação.

Important

Se você estiver escrevendo um UWP app, consulte a documentação da UWP. Para outras linguagens de programação desktop, consulte Desktop C#.

Etapa 1: Habilitar o SDK do Windows

Se você não habilitou o SDK do Windows para o seu app, então você deve fazer isso primeiro. Há algumas etapas principais.

  1. Adicione runtimeobject.lib a dependências adicionais .
  2. Direcione o SDK do Windows.

Clique com o botão direito do mouse no projeto e selecione Propriedades.

No menu de Configuração superior, selecione Todas as Configurações para que a alteração a seguir seja aplicada tanto à Depuração quanto ao Release.

Em Vinculador -> Entrada, adicione runtimeobject.lib às Dependências Adicionais .

Em seguida, em Geral, verifique se a Versão do SDK do Windows está definida como a versão 10.0 ou posterior.

Etapa 2: Copiar código da biblioteca de compatibilidade

Copie o arquivo DesktopNotificationManagerCompat.h e DesktopNotificationManagerCompat.cpp do GitHub para seu projeto. A biblioteca de compatibilidade abstrai grande parte da complexidade das notificações da área de trabalho. As instruções a seguir exigem a biblioteca compat.

Se você estiver usando cabeçalhos pré-compilados, certifique-se de colocar #include "stdafx.h" como a primeira linha do arquivo DesktopNotificationManagerCompat.cpp.

Etapa 3: Incluir os arquivos de cabeçalho e namespaces

Inclua o arquivo de cabeçalho da biblioteca de compatibilidade e os arquivos de cabeçalho e namespaces relacionados ao uso das APIs de notificação do Windows.

#include "DesktopNotificationManagerCompat.h"
#include <NotificationActivationCallback.h>
#include <windows.ui.notifications.h>

using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::UI::Notifications;
using namespace Microsoft::WRL;

Etapa 4: Implementar o ativador

Você deve implementar um manipulador para a ativação da notificação, para que, quando o usuário clicar na sua notificação, seu app possa fazer algo. Isso é necessário para que sua notificação persista na Central de Ações (já que a notificação pode ser clicada dias depois, quando a app está fechada). Essa classe pode ser colocada em qualquer lugar em seu projeto.

Implemente a interface INotificationActivationCallback, conforme visto abaixo, incluindo um UUID e também chame CoCreatableClass para sinalizar sua classe como creatável COM. Para sua UUID, crie um GUID exclusivo usando um dos muitos geradores GUID online. Esse GUID CLSID (identificador de classe) é como a Central de Ações sabe qual classe ativar com COM.

// The UUID CLSID must be unique to your app. Create a new GUID if copying this code.
class DECLSPEC_UUID("replaced-with-your-guid-C173E6ADF0C3") NotificationActivator WrlSealed WrlFinal
    : public RuntimeClass<RuntimeClassFlags<ClassicCom>, INotificationActivationCallback>
{
public:
    virtual HRESULT STDMETHODCALLTYPE Activate(
        _In_ LPCWSTR appUserModelId,
        _In_ LPCWSTR invokedArgs,
        _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA* data,
        ULONG dataCount) override
    {
        // TODO: Handle activation
    }
};

// Flag class as COM creatable
CoCreatableClass(NotificationActivator);

Etapa 5: Registrar-se com a plataforma de notificação

Em seguida, você deve se registrar na plataforma de notificação. Dependendo se o seu app está empacotado ou descompactado, há etapas diferentes. Se você der suporte a ambos, deverá executar ambos os conjuntos de etapas (no entanto, não é necessário bifurcar seu código, pois nossa biblioteca manipula isso para você).

Packaged

Se o seu app estiver empacotado (consulte Criar um novo projeto para uma área de trabalho empacotada do WinUI 3 app) ou empacotado com local externo (consulte Conceder identidade do pacote empacotando com local externo), ou se oferecer suporte a ambos, então no seu Package.appxmanifest adicione:

  1. Declaração para xmlns:com
  2. Declaração para xmlns:desktop
  3. No atributo IgnorableNamespaces, com e desktop
  4. com: extensão para o ativador COM usando o GUID da etapa 4. Certifique-se de incluir o Arguments="-ToastActivated" para saber que o seu lançamento foi a partir de uma notificação app.
  5. desktop:Extensão para windows.toastNotificationActivation declarar o CLSID do ativador de notificações app (o GUID do passo #4).

Package.appxmanifest

<Package
  ...
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="... com desktop">
  ...
  <Applications>
    <Application>
      ...
      <Extensions>

        <!--Register COM CLSID LocalServer32 registry key-->
        <com:Extension Category="windows.comServer">
          <com:ComServer>
            <com:ExeServer Executable="YourProject\YourProject.exe" Arguments="-ToastActivated" DisplayName="Toast activator">
              <com:Class Id="replaced-with-your-guid-C173E6ADF0C3" DisplayName="Toast activator"/>
            </com:ExeServer>
          </com:ComServer>
        </com:Extension>

        <!--Specify which CLSID to activate when toast clicked-->
        <desktop:Extension Category="windows.toastNotificationActivation">
          <desktop:ToastNotificationActivation ToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> 
        </desktop:Extension>

      </Extensions>
    </Application>
  </Applications>
 </Package>

Unpackaged

Se seu app estiver desempacotado (consulte Criar um novo projeto para um aplicativo de desktop WinUI 3 não empacotado app), ou se você oferecer suporte a ambos, deverá declarar seu ID do Modelo de Usuário do Aplicativo (AUMID) e toast o CLSID do ativador (o GUID da etapa 4) no atalho do seu app no Menu Iniciar.

Escolha um AUMID exclusivo que identificará sua app. Isso normalmente está na forma de [CompanyName]. [AppName]. Mas você deseja garantir que ele seja exclusivo em todos os aplicativos (portanto, fique à vontade para adicionar alguns dígitos no final).

Etapa 5.1: Instalador do WiX

Se você estiver usando o WiX para o instalador, edite o arquivo Product.wxs para adicionar as duas propriedades de atalho ao atalho do menu Iniciar, conforme visto abaixo. Certifique-se de que o GUID da etapa nº 4 esteja dentro de {}, conforme visto abaixo.

Product.wxs

<Shortcut Id="ApplicationStartMenuShortcut" Name="Wix Sample" Description="Wix Sample" Target="[INSTALLFOLDER]WixSample.exe" WorkingDirectory="INSTALLFOLDER">
                    
    <!--AUMID-->
    <ShortcutProperty Key="System.AppUserModel.ID" Value="YourCompany.YourApp"/>
    
    <!--COM CLSID-->
    <ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID" Value="{replaced-with-your-guid-C173E6ADF0C3}"/>
    
</Shortcut>

Important

Para realmente usar notificações, você deve instalar o app instalador uma vez antes da depuração normalmente, para que o atalho Iniciar com seu AUMID e CLSID esteja presente. Depois que o atalho Iniciar estiver presente, você poderá depurar usando F5 do Visual Studio.

Etapa 5.2: Registrar servidor AUMID e COM

Em seguida, independentemente do instalador, no appcódigo de inicialização (antes de chamar as APIs de notificação), chame o método RegisterAumidAndComServer, especificando sua classe de ativador de notificação da etapa #4 e o AUMID usado acima.

// Register AUMID and COM server (for a packaged app, this is a no-operation)
hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(L"YourCompany.YourApp", __uuidof(NotificationActivator));

Se o seu app suporta a implantação empacotada e não empacotada, fique à vontade para chamar esse método independentemente. Se você estiver executando empacotado (ou seja, com a identidade do pacote em runtime), esse método simplesmente retornará imediatamente. Não é necessário bifurcar seu código.

Esse método permite que você chame as APIs de compatibilidade para enviar e gerenciar notificações sem precisar fornecer constantemente seu AUMID. E insere a chave do Registro LocalServer32 para o servidor COM.

Etapa 6: Registrar o ativador COM

Para aplicativos empacotados e não empacotados, você deve registrar o tipo de ativador de notificação para que possa lidar com toast ativações.

appNo código de inicialização, chame o seguinte método RegisterActivator. Isso deve ser chamado para que você receba todas toast as ativações.

// Register activator type
hr = DesktopNotificationManagerCompat::RegisterActivator();

Etapa 7: Enviar uma notificação

Enviar uma notificação é idêntico a aplicativos UWP, exceto que você usará DesktopNotificationManagerCompat para criar um ToastNotifier. A biblioteca de compatibilidade manipula automaticamente a diferença entre aplicativos empacotados e não empacotados, portanto, você não precisa bifurcar seu código. Para um app sem pacote, a biblioteca de compatibilidade armazena em cache o AUMID que você forneceu quando chamou RegisterAumidAndComServer, de modo que você não precise se preocupar sobre quando deve ou não fornecer o AUMID.

Use a associação ToastGeneric , conforme visto abaixo, já que os modelos de notificação herdados do Windows 8.1 toast não ativarão o ativador de notificação COM que você criou na etapa 4.

Important

Há suporte para imagens http somente em aplicativos empacotados que têm a funcionalidade da Internet em seu manifesto. Aplicativos não empacotados não dão suporte a imagens http; você deve baixar a imagem para seus dados locais app e referenciá-la localmente.

// Construct XML
ComPtr<IXmlDocument> doc;
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(
    L"<toast><visual><binding template='ToastGeneric'><text>Hello world</text></binding></visual></toast>",
    &doc);
if (SUCCEEDED(hr))
{
    // See full code sample to learn how to inject dynamic text, buttons, and more

    // Create the notifier
    // Desktop apps must use the compat method to create the notifier.
    ComPtr<IToastNotifier> notifier;
    hr = DesktopNotificationManagerCompat::CreateToastNotifier(&notifier);
    if (SUCCEEDED(hr))
    {
        // Create the notification itself (using helper method from compat library)
        ComPtr<IToastNotification> toast;
        hr = DesktopNotificationManagerCompat::CreateToastNotification(doc.Get(), &toast);
        if (SUCCEEDED(hr))
        {
            // And show it!
            hr = notifier->Show(toast.Get());
        }
    }
}

Important

Os aplicativos de desktop não podem usar modelos legados toast (como ToastText02). A ativação dos modelos herdados falhará quando o COM CLSID for especificado. Você deve usar os modelos do Windows ToastGeneric, conforme visto acima.

Etapa 8: Gerenciar a ativação

Quando o usuário clica em sua app notificação ou botões na notificação, o método Ativar da classe NotificationActivator é invocado.

Dentro do método Activate, você pode analisar os args especificados na notificação e obter a entrada do usuário que o usuário digitou ou selecionou e, em seguida, ativar o seu app de acordo.

Note

O método Activate é chamado em um thread separado do thread principal.

// The GUID must be unique to your app. Create a new GUID if copying this code.
class DECLSPEC_UUID("replaced-with-your-guid-C173E6ADF0C3") NotificationActivator WrlSealed WrlFinal
    : public RuntimeClass<RuntimeClassFlags<ClassicCom>, INotificationActivationCallback>
{
public: 
    virtual HRESULT STDMETHODCALLTYPE Activate(
        _In_ LPCWSTR appUserModelId,
        _In_ LPCWSTR invokedArgs,
        _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA* data,
        ULONG dataCount) override
    {
        std::wstring arguments(invokedArgs);
        HRESULT hr = S_OK;

        // Background: Quick reply to the conversation
        if (arguments.find(L"action=reply") == 0)
        {
            // Get the response user typed.
            // We know this is first and only user input since our toasts only have one input
            LPCWSTR response = data[0].Value;

            hr = DesktopToastsApp::SendResponse(response);
        }

        else
        {
            // The remaining scenarios are foreground activations,
            // so we first make sure we have a window open and in foreground
            hr = DesktopToastsApp::GetInstance()->OpenWindowIfNeeded();
            if (SUCCEEDED(hr))
            {
                // Open the image
                if (arguments.find(L"action=viewImage") == 0)
                {
                    hr = DesktopToastsApp::GetInstance()->OpenImage();
                }

                // Open the app itself
                // User might have clicked on app title in Action Center which launches with empty args
                else
                {
                    // Nothing to do, already launched
                }
            }
        }

        if (FAILED(hr))
        {
            // Log failed HRESULT
        }

        return S_OK;
    }

    ~NotificationActivator()
    {
        // If we don't have window open
        if (!DesktopToastsApp::GetInstance()->HasWindow())
        {
            // Exit (this is for background activation scenarios)
            exit(0);
        }
    }
};

// Flag class as COM creatable
CoCreatableClass(NotificationActivator);

Para garantir que o início ocorra corretamente enquanto app estiver fechado, na sua função WinMain, você deve determinar se está sendo iniciado a partir de uma notificação app ou não. Se iniciado a partir de uma notificação, haverá um argumento de inicialização "-ToastActivated". Quando vir isso, você deve parar de executar qualquer código de ativação de inicialização normal e permitir que seu NotificationActivator para lidar com janelas de inicialização, se necessário.

// Main function
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE, _In_ LPWSTR cmdLineArgs, _In_ int)
{
    RoInitializeWrapper winRtInitializer(RO_INIT_MULTITHREADED);

    HRESULT hr = winRtInitializer;
    if (SUCCEEDED(hr))
    {
        // Register AUMID and COM server (for a packaged app, this is a no-operation)
        hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(L"WindowsNotifications.DesktopToastsCpp", __uuidof(NotificationActivator));
        if (SUCCEEDED(hr))
        {
            // Register activator type
            hr = DesktopNotificationManagerCompat::RegisterActivator();
            if (SUCCEEDED(hr))
            {
                DesktopToastsApp app;
                app.SetHInstance(hInstance);

                std::wstring cmdLineArgsStr(cmdLineArgs);

                // If launched from toast
                if (cmdLineArgsStr.find(TOAST_ACTIVATED_LAUNCH_ARG) != std::string::npos)
                {
                    // Let our NotificationActivator handle activation
                }

                else
                {
                    // Otherwise launch like normal
                    app.Initialize(hInstance);
                }

                app.RunMessageLoop();
            }
        }
    }

    return SUCCEEDED(hr);
}

Sequência de ativação de eventos

A sequência de ativação é a seguinte...

Se você app já estiver em execução:

  1. Ativar no NotificationActivator é invocado

Se você app não estiver em execução:

  1. Seu app exe é iniciado, você obtém um args de linha de comando de "-ToastActivated"
  2. Ativar no NotificationActivator é invocado

Primeiro plano versus ativação em segundo plano

Para aplicativos da área de trabalho, a ativação de primeiro e segundo plano é tratada de forma idêntica – o ativador COM é chamado. Cabe ao código appdecidir se uma janela deve ser exibida ou simplesmente executar algum trabalho e, em seguida, sair. Portanto, especificar um tipo de ativação em segundo plano no app conteúdo da notificação não altera o comportamento.

Etapa 9: Remover e gerenciar notificações

A remoção e o gerenciamento de notificações são idênticos aos aplicativos UWP. No entanto, recomendamos que você use nossa biblioteca de compatibilidade para obter um DesktopNotificationHistoryCompat para que você não precise se preocupar em fornecer o AUMID para uma área de trabalho app.

std::unique_ptr<DesktopNotificationHistoryCompat> history;
auto hr = DesktopNotificationManagerCompat::get_History(&history);
if (SUCCEEDED(hr))
{
    // Remove a specific toast
    hr = history->Remove(L"Message2");

    // Clear all toasts
    hr = history->Clear();
}

Etapa 10: Implantação e depuração

Para implantar e depurar seu pacoteapp, consulte Executar, depurar e testar um aplicativo de desktop appempacotado.

Para implantar e depurar sua aplicação de desktop app, você deve instalar seu app através do instalador uma vez antes de depurar normalmente, para que o atalho Iniciar com o AUMID e o CLSID seja criado. Depois que o atalho Iniciar estiver presente, você poderá depurar usando F5 do Visual Studio.

Se as notificações simplesmente não aparecerem na área de trabalho app (e nenhuma exceção for gerada), isso provavelmente significará que o atalho Iniciar não está presente (instale-o app por meio do instalador) ou o AUMID usado no código não corresponderá ao AUMID no atalho Iniciar.

Se suas notificações aparecerem, mas não forem mantidas na Central de Ações (desaparecendo depois que o pop-up for ignorado), isso significa que você não implementou o ativador COM corretamente.

Se você tiver instalado sua área de trabalho appempacotada e descompactada, observe que o empacotado app substituirá o desempacotado app ao lidar com toast ativações. Isso significa que app as notificações do desempacotado app iniciarão o empacotado app quando clicadas. A desinstalação do empacotado app vai reverter as ativações para a forma não empacotada app.

Se você receber HRESULT 0x800401f0 CoInitialize has not been called., certifique-se de ligar para CoInitialize(nullptr) no seu app antes de chamar as APIs.

Se você receber HRESULT 0x8000000e A method was called at an unexpected time. ao chamar as APIs de compatibilidade, isso provavelmente significa que você não conseguiu chamar os métodos Register necessários (ou, caso seja um app empacotado, você não está executando o app no contexto empacotado).

Se você receber vários erros de compilação unresolved external symbol, provavelmente esqueceu de adicionar runtimeobject.lib às Dependências Adicionais na etapa nº 1 (ou só o adicionou à configuração de Depuração e não à configuração de Release).

Manipular versões mais antigas do Windows

Se você der suporte ao Windows 8.1 ou inferior, convém verificar em runtime se você está executando no Windows antes de chamar qualquer DESKTOPNotificationManagerCompat APIs ou enviar qualquer notificação do ToastGeneric.

O Windows 8 introduziu toast notificações, mas usou os modelos herdadostoast, como ToastText01. A ativação foi tratada pelo evento Activated na memória na classe ToastNotification, uma vez que as notificações eram apenas breves pop-ups que não eram persistidas. O Windows 10 introduziu notificação interativa do ToastGenerice também introduziu a Central de Ações em que as notificações são mantidas por vários dias. A implementação da Central de Ações exigiu a introdução de um ativador COM, para que o seu toast possa ser ativado dias após você o ter criado.

OS ToastGeneric Ativador COM Modelos toast herdados
Windows 10 e posterior Supported Supported Com suporte (mas não ativa o servidor COM)
Windows 8.1 / 8 N/A N/A Supported
Windows 7 e inferior N/A N/A N/A

Para verificar se você está usando o Windows 10 ou posterior, inclua o cabeçalho <VersionHelpers.h> e verifique o método IsWindows10OrGreater. Se isso retornar true, continue chamando todos os métodos descritos nesta documentação.

#include <VersionHelpers.h>

if (IsWindows10OrGreater())
{
    // Running on Windows 10 or later, continue with sending toasts!
}

Problemas conhecidos

CORRIGIDO: App não fica focado após toast clicar: nos builds 15063 e anteriores, os direitos de primeiro plano não estavam sendo transferidos para seu aplicativo quando ativávamos o servidor COM. Portanto, seu app ficaria piscando quando você tentasse movê-lo para a frente. Não havia solução alternativa para esta questão. Corrigimos isso nos builds 16299 ou posteriores.

Resources