Envoi d'une notification Toast locale à partir d'une application de bureau C++ WRL

Les applications de bureau packagées et non packagées peuvent envoyer des notifications Toast interactives, tout comme les applications de la plateforme universelle de Windows (UWP). Cela inclut les applications packagées (consultez Créer un nouveau projet pour une application de bureau WinUI 3 packagée), les applications packagées avec un emplacement externe (consultez Octroyer l'identité du package en le packagant avec un emplacement externe) et les applications non packagées (consultez Créer un nouveau projet pour une application de bureau WinUI 3 non packagée).

Cependant, pour une application de bureau non packagée, il y a quelques étapes spéciales. Cela est dû aux différents schémas d'activation, et à l'absence d'identité du package au moment de l'exécution.

Important

Si vous écrivez une application UWP, veuillez consulter la documentation UWP. Pour d'autres langages de bureau, veuillez consulter Desktop C#.

Étape 1 : Activer le SDK Windows

Si vous n'avez pas activé le SDK Windows pour votre application, vous devez d'abord le faire. Il y a quelques étapes clés.

  1. Ajouter runtimeobject.lib aux dépendances supplémentaires.
  2. Ciblez le SDK Windows.

Cliquez avec le bouton droit sur votre projet et sélectionnez Propriétés.

Dans le menu supérieur Configuration, sélectionnez Toutes les configurations afin que la modification suivante soit appliquée à la fois à Debug et à Release.

Sous Linker -> Input, ajoutez runtimeobject.lib aux dépendances supplémentaires.

Ensuite, sous Général, assurez-vous que la version du SDK Windows est réglée sur la version 10.0 ou une version ultérieure.

Étape 2 : Copier le code de la bibliothèque compat

Copiez les fichiers DesktopNotificationManagerCompat.h et DesktopNotificationManagerCompat.cpp de GitHub dans votre projet. La bibliothèque compat fait abstraction d'une grande partie de la complexité des notifications de bureau. Les instructions suivantes nécessitent la bibliothèque compat.

Si vous utilisez des en-têtes précompilés, assurez-vous de #include "stdafx.h" comme première ligne du fichier DesktopNotificationManagerCompat.cpp.

Étape 3 : Inclure les fichiers d'en-tête et les espaces de noms

Incluez le fichier d'en-tête de la bibliothèque compat, ainsi que les fichiers d'en-tête et les espaces de noms liés à l'utilisation des API de Windows Toast.

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

Étape 4 : Implémentation de l'activateur

Vous devez mettre en œuvre un gestionnaire d'activation de Toast, de manière à ce que lorsque l'utilisateur clique sur votre Toast, votre application puisse faire quelque chose. Cela est nécessaire pour que votre Toast persiste dans le Centre d'action (puisque le Toast peut être cliqué quelques jours plus tard lorsque votre application est fermée). Cette classe peut être placée n'importe où dans votre projet.

Mettez en œuvre l'interface INotificationActivationCallback comme indiqué ci-dessous, en y incluant un UUID, et appelez également CoCreatableClass pour indiquer que votre classe est COM creatable. Pour votre UUID, créez un GUID unique en utilisant l'un des nombreux générateurs de GUID en ligne. Ce GUID CLSID (identifiant de classe) permet à Action Center de savoir quelle classe doit être activée par 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);

Étape 5 : S'inscrire à la plate-forme de notification

Vous devez ensuite vous enregistrer auprès de la plateforme de notification. Les étapes sont différentes selon que votre application est packagée ou non. Si vous prenez en charge les deux, vous devez effectuer les deux séries d'étapes (cependant, il n'est pas nécessaire de forker votre code puisque notre bibliothèque s'en charge pour vous).

Empaqueté

Si votre application est packagée (voir Créer un nouveau projet pour une application de bureau WinUI 3 packagée) ou packagée avec un emplacement externe (voir Octroyer l'identité du package en packagant avec un emplacement externe), ou si vous prenez en charge les deux, alors dans votre Package.appxmanifest, ajoutez :

  1. Déclaration pour xmlns:com
  2. Déclaration pour xmlns:desktop
  3. Dans l'attribut IgnorableNamespaces, com et desktop
  4. com:Extension pour l'activateur COM en utilisant le GUID de l'étape #4. Veillez à inclure le Arguments="-ToastActivated" pour que vous sachiez que votre lancement a été effectué à partir d'un toast
  5. desktop:Extension for windows.toastNotificationActivation pour déclarer le CLSID de votre activateur de toast (le GUID de l'étape 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>

Non empaqueté

Si votre application n'est pas packagée (voir Créer un nouveau projet pour une application de bureau WinUI 3 non packagée), ou si vous prenez en charge les deux, vous devez alors déclarer votre Application User Model ID (AUMID) et le CLSID de l'activateur de toast (le GUID de l'étape 4) sur le raccourci de votre application dans Start.

Choisissez un AUMID unique qui identifiera votre application. C'est typiquement sous la forme de [CompanyName].[AppName]. Mais vous voulez vous assurer qu'il est unique pour toutes les applications (donc n'hésitez pas à ajouter quelques chiffres à la fin).

Etape 5.1 : Installateur WiX

Si vous utilisez WiX pour votre installateur, modifiez le fichier Product.wxs pour ajouter les deux propriétés de raccourci à votre raccourci du menu Démarrer, comme indiqué ci-dessous. Veillez à ce que le GUID obtenu à l'étape 4 soit entouré de {}, comme indiqué ci-dessous.

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

Dans l'ordonnanceur, vous devez installer votre application à travers l'installateur une fois avant de déboguer normalement, de manière à ce que le raccourci de démarrage avec votre AUMID et CLSID soit présent. Une fois que le raccourci de démarrage est présent, vous pouvez déboguer en utilisant F5 à partir de Visual Studio.

Étape 5.2 : Enregistrer l'AUMID et le serveur COM

Ensuite, quel que soit votre programme d'installation, dans le code de démarrage de votre application (avant d'appeler toute API de notification), appelez la méthode RegisterAumidAndComServer, en spécifiant votre classe d'activateur de notification de l'étape 4 et votre AUMID utilisé ci-dessus.

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

Si votre application prend en charge les déploiements avec ou sans package, n'hésitez pas à appeler cette méthode. Si vous utilisez une application packagée (c'est-à-dire avec l'identité du package au moment de l'exécution), cette méthode renverra immédiatement. Il n'est pas nécessaire de forker votre code.

Cette méthode vous permet d'appeler les API compat pour envoyer et gérer les notifications sans avoir à fournir constamment votre AUMID. Et elle insère la clé de registre LocalServer32 pour le serveur COM.

Étape 6 : Enregistrer l'activateur COM

Pour les applications packagées et non packagées, vous devez enregistrer votre type d'activateur de notification, afin de pouvoir gérer les activations Toast.

Dans le code de démarrage de votre application, appelez la méthode RegisterActivator suivante. Cet activateur doit être appelé pour que vous puissiez recevoir des activations Toast.

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

Étape 7 : Envoyer une notification

L'envoi d'une notification est identique aux applications UWP, sauf que vous utiliserez DesktopNotificationManagerCompat pour créer un ToastNotifier. La bibliothèque compat gère automatiquement la différence entre les applications packagées et non packagées, vous n'avez donc pas besoin de forker votre code. Pour une application non packagée, la bibliothèque compat met en cache l'AUMID que vous avez fourni lorsque vous avez appelé RegisterAumidAndComServer, de sorte que vous n'avez pas à vous soucier de savoir quand fournir ou non l'AUMID.

Veillez à utiliser la liaison ToastGeneric comme indiqué ci-dessous, car les modèles notification de Toast de Windows 8.1 n'activeront pas l'activateur de notification COM que vous avez créé à l'étape 4.

Important

Les images Http ne sont prises en charge que dans les applications packageés qui ont la capacité internet dans leur manifeste. Les applications non packagées ne prennent pas en charge les images http ; vous devez télécharger l'image dans les données de votre application locale et y faire référence localement.

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

Les applications de bureau ne peuvent pas utiliser les anciens modèles Toast (tels que ToastText02). L'activation de ces modèles échouera si le CLSID COM est spécifié. Vous devez utiliser les modèles ToastGeneric de Windows, comme indiqué ci-dessus.

Étape 8 : Gestion de l'activation

Lorsque l'utilisateur clique sur votre toast ou sur les boutons du toast, la méthode Activate de votre classe NotificationActivator est invoquée.

Dans la méthode Activate, vous pouvez analyser les applications que vous avez spécifiées dans le Toast et obtenir les entrées que l'utilisateur a tapées ou sélectionnées, puis activer votre application en conséquence.

Remarque

La méthode Activate est appelée dans un thread distinct de votre 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);

Pour prendre en charge correctement le lancement lorsque votre application est fermée, dans votre fonction WinMain, vous devez déterminer si vous êtes lancé à partir d'un Toast ou non. Si vous êtes lancé à partir d'un Toast, l'arg de lancement sera « -ToastActivated ». Lorsque vous voyez cela, vous devez cesser d'exécuter tout code d'activation de lancement normal, et permettre à votre NotificationActivator de décrire les fenêtres de lancement si nécessaire.

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

Séquence d'événements de l'activation

La séquence d'activation est la suivante...

Si votre application est déjà en cours d'exécution :

  1. Activate est appelé dans votre NotificationActivator

Si votre application n'est pas en cours d'exécution :

  1. Votre application est lancée par EXE, vous obtenez un argue de ligne de commande de « -ToastActivated »
  2. Activate est appelé dans votre NotificationActivator

Activation en avant-plan ou en arrière-plan

Pour les applications de bureau, l'activation en avant-plan et en arrière-plan est gérée de la même manière : votre activateur COM est appelé. C'est au code de votre application de décider s'il faut afficher une fenêtre ou simplement effectuer un travail puis quitter. Par conséquent, le fait de spécifier un activationType de background dans votre contenu de toast ne change pas le comportement.

Étape 9 : Supprimer et gérer les notifications

La suppression et la gestion des notifications sont identiques à celles des applications UWP. Cependant, nous vous recommandons d'utiliser notre bibliothèque compat pour obtenir un DesktopNotificationHistoryCompat afin que vous n'ayez pas à vous soucier de fournir l'AUMID pour une application de bureau.

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

Étape 10 : Déploiement et débogage

Pour déployer et déboguer votre application packagée, consultez Exécuter, déboguer et tester une application de bureau packagée.

Pour déployer et déboguer votre application de bureau, vous devez installer votre application via le programme d'installation une fois avant de déboguer normalement, afin que le raccourci de démarrage avec votre AUMID et votre CLSID soit présent. Une fois que le raccourci de démarrage est présent, vous pouvez déboguer en utilisant F5 à partir de Visual Studio.

Si vos notifications n'apparaissent pas dans votre application de bureau (et qu'aucune exception n'est levée), cela signifie probablement que le raccourci de démarrage n'est pas présent (installez votre application via le programme d'installation), ou que l'AUMID que vous avez utilisé dans le code ne correspond pas à l'AUMID de votre raccourci de démarrage.

Si vos notifications apparaissent mais ne sont pas persistantes dans le Centre d'action (elles disparaissent après que la fenêtre contextuelle a été rejetée), cela signifie que vous n'avez pas implémenté l'activateur COM correctement.

Si vous avez installé à la fois votre application de bureau packagée et non packagée, notez que l'application packagée remplacera l'application non packagée lors de la gestion des applications Toast. Cela signifie que les toasts de l'application non packagée lanceront l'application packagée lorsque vous cliquerez dessus. La désinstallation de l'application packagée rétablira les activations à partir de l'application non packagée.

Si vous recevez HRESULT 0x800401f0 CoInitialize has not been called., veillez à appeler CoInitialize(nullptr) dans votre application avant d'appeler les API.

Si vous recevez HRESULT 0x8000000e A method was called at an unexpected time. lorsque vous appelez les applications Compat, cela signifie probablement que vous n'avez pas appelé les méthodes d'enregistrement requises (ou, s'il s'agit d'une application packagée, que vous n'exécutez pas actuellement votre application dans le contexte packagé).

Si vous obtenez de nombreuses erreurs de compilation unresolved external symbol, vous avez probablement oublié d'ajouter runtimeobject.lib aux Additional Dependencies à l'étape #1 (ou vous ne l'avez ajouté qu'à la configuration Debug et non à la configuration Release).

Gestion des anciennes versions de Windows

Si vous prenez en charge Windows 8.1 ou une version inférieure, vous devrez vérifier au moment de l'exécution si vous travaillez sous Windows avant d'appeler les API DesktopNotificationManagerCompat ou d'envoyer des toasts ToastGeneric.

Windows 8 a introduit les notifications toast, mais a utilisé les anciens modèles de toast, comme ToastText01. L'activation était gérée par l'événement Activated en mémoire de la classe ToastNotification, car les toasts n'étaient que de brèves fenêtres contextuelles qui n'étaient pas conservées. Windows 10 a introduit les toasts interactifs ToastGeneric, ainsi que le Centre d'action où les notifications sont conservées pendant plusieurs jours. L'introduction du Centre d'action a nécessité l'introduction d'un activateur COM, afin que votre toast puisse être activé quelques jours après sa création.

SE ToastGeneric Activateur COM Anciens modèles de Toast
Windows 10 et versions ultérieures Prise en charge Prise en charge Pris en charge (mais n'active pas le serveur COM)
Windows 8.1 / 8 N/A N/A Prise en charge
Windows 7 et versions inférieures N/A N/A N/A

Pour vérifier si vous êtes sous Windows 10 ou une version ultérieure, incluez l'en-tête <VersionHelpers.h>, et vérifiez la méthode IsWindows10OrGreater. Si le résultat est true, continuez à appeler toutes les méthodes décrites dans cette documentation.

#include <VersionHelpers.h>

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

Problèmes connus

CORRIGÉ : L'application ne devient pas centrée après avoir cliqué sur toast : Dans les builds 15063 et antérieures, les droits de premier plan n'étaient pas transférés à votre application lorsque nous activions le serveur COM. Par conséquent, votre application clignoterait simplement lorsque vous tenteriez de la faire passer au premier plan. Il n’existait aucune solution de contournement pour ce problème. Nous avons corrigé ce problème dans les versions 16299 ou ultérieures.

Ressources