Inviare una notifica di tipo avviso popup locale da un'app desktop C++ WRL

Le app desktop in pacchetto e non in pacchetto possono inviare notifiche di tipo avviso popup interattive proprio come le app UWP (Universal Windows Platform). Che include app in pacchetto (vedi Creare un nuovo progetto per un'app desktop WinUI 3 in pacchetto); app in pacchetto con posizione esterna (vedi Concedere l'identità del pacchetto tramite la creazione di pacchetti con posizione esterna) e app non in pacchetto (vedi Creare un nuovo progetto per un'app desktop WinUI 3 non in pacchetto).

Tuttavia, per un'app desktop non in pacchetto, esistono alcuni passaggi speciali. Ciò è dovuto ai diversi schemi di attivazione e alla mancanza di identità del pacchetto in fase di esecuzione.

Importante

Se stai scrivendo un'app UWP, vedi la documentazione della piattaforma UWP. Per altri linguaggi desktop, vedere Desktop C#.

Passaggio 1: Abilitare Windows SDK

Se non hai abilitato Windows SDK per la tua app, devi prima farlo. Esistono alcuni passaggi chiave.

  1. Aggiungere runtimeobject.lib a Dipendenze aggiuntive.
  2. Specificare come destinazione Windows SDK.

Fare clic con il pulsante destro del mouse sul progetto e scegliere Proprietà.

Nel menu Configurazione in alto selezionare Tutte le configurazioni in modo che la modifica seguente venga applicata sia a Debug che a Release.

In Linker -> Input aggiungere runtimeobject.lib alle dipendenze aggiuntive.

In Generale assicurarsi quindi che la versione di Windows SDK sia impostata sulla versione 10.0 o successiva.

Passaggio 2: Copiare il codice della libreria di compatibilità

Copiare il file DesktopNotificationManagerCompat.h e DesktopNotificationManagerCompat.cpp da GitHub nel progetto. La libreria di compatibilità astrae gran parte della complessità delle notifiche desktop. Le istruzioni seguenti richiedono la libreria di compatibilità.

Se si usano intestazioni precompilate, assicurarsi di #include "stdafx.h" essere la prima riga del file DesktopNotificationManagerCompat.cpp.

Passaggio 3: Includere i file di intestazione e gli spazi dei nomi

Includere il file di intestazione della libreria di compatibilità e i file di intestazione e gli spazi dei nomi correlati all'uso delle API di tipo avviso popup di 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;

Passaggio 4: Implementare l'attivatore

Devi implementare un gestore per l'attivazione degli avvisi popup, in modo che quando l'utente fa clic sull'avviso popup, l'app può eseguire un'operazione. Questa operazione è necessaria per rendere persistente l'avviso popup nel Centro notifiche( poiché l'avviso popup potrebbe essere selezionato giorni dopo la chiusura dell'app). Questa classe può essere inserita in qualsiasi punto del progetto.

Implementare l'interfaccia INotificationActivationCallback come illustrato di seguito, incluso un UUID, e chiamare anche CoCreatableClass per contrassegnare la classe come creabile COM. Per l'UUID, creare un GUID univoco usando uno dei numerosi generatori GUID online. Questo GUID CLSID (identificatore di classe) è il modo in cui il Centro notifiche sa quale classe attivare 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);

Passaggio 5: Eseguire la registrazione con la piattaforma di notifica

È quindi necessario registrarsi con la piattaforma di notifica. Esistono passaggi diversi a seconda che l'app sia in pacchetto o non in pacchetto. Se si supportano entrambi, è necessario eseguire entrambi i set di passaggi( tuttavia, non è necessario creare una copia tramite fork del codice perché la libreria gestisce automaticamente tale operazione).

Incluso nel pacchetto

Se l'app viene inserita in un pacchetto (vedi Creare un nuovo progetto per un'app desktop WinUI 3 in pacchetto) o inserita in un pacchetto con un percorso esterno (vedi Concedere l'identità del pacchetto tramite la creazione di pacchetti con posizione esterna) o se si supportano entrambe, nell'aggiunta di Package.appxmanifest:

  1. Dichiarazione per xmlns:com
  2. Dichiarazione per xmlns:desktop
  3. Nell'attributo IgnorableNamespaces , com e desktop
  4. com:Extension per l'attivatore COM usando il GUID del passaggio 4. Assicurarsi di includere in Arguments="-ToastActivated" modo da sapere che il lancio è stato da un avviso popup
  5. desktop:Extension per windows.toastNotificationActivation per dichiarare CLSID dell'attivatore dell'avviso popup (GUID del passaggio 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 l'app non è in pacchetto (vedi Creare un nuovo progetto per un'app desktop WinUI 3 senza pacchetti) o se si supportano entrambe, è necessario dichiarare l'ID modello utente applicazione (AUMID) e CLSID dell'attivatore di tipo avviso popup (GUID del passaggio 4) nel collegamento dell'app in Start.

Selezionare un AUMID univoco che identificherà l'app. Si tratta in genere sotto forma di [CompanyName]. [AppName]. Ma si vuole assicurarsi che sia univoco in tutte le app (quindi è possibile aggiungere alcune cifre alla fine).

Passaggio 5.1: Programma di installazione WiX

Se si usa WiX per il programma di installazione, modificare il file Product.wxs per aggiungere le due proprietà di collegamento al collegamento menu Start come illustrato di seguito. Assicurarsi che il GUID del passaggio 4 sia racchiuso come {} illustrato di seguito.

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>

Importante

Per usare effettivamente le notifiche, è necessario installare l'app tramite il programma di installazione una sola volta prima del debug normalmente, in modo che sia presente il collegamento Start con AUMID e CLSID. Dopo aver visualizzato il collegamento Start, è possibile eseguire il debug usando F5 da Visual Studio.

Passaggio 5.2: Registrare aumid e server COM

Quindi, indipendentemente dal programma di installazione, nel codice di avvio dell'app (prima di chiamare qualsiasi API di notifica), chiamare il metodo RegisterAumidAndComServer, specificando la classe di attivazione delle notifiche dal passaggio 4 e l'AUMID usato in precedenza.

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

Se l'app supporta sia la distribuzione in pacchetto che quella non in pacchetto, è possibile chiamare questo metodo indipendentemente. Se si esegue un pacchetto (ovvero con l'identità del pacchetto in fase di esecuzione), questo metodo restituirà semplicemente immediatamente. Non è necessario creare una copia tramite fork del codice.

Questo metodo consente di chiamare le API di compatibilità per inviare e gestire le notifiche senza dover fornire costantemente l'AUMID. Inserisce la chiave del Registro di sistema LocalServer32 per il server COM.

Passaggio 6: Registrare l'attivatore COM

Per le app in pacchetto e non in pacchetto, devi registrare il tipo di attivatore di notifica, in modo da poter gestire le attivazioni di tipo avviso popup.

Nel codice di avvio dell'app chiamare il metodo RegisterActivator seguente. Questa operazione deve essere chiamata per poter ricevere eventuali attivazioni di tipo avviso popup.

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

Passaggio 7: Inviare una notifica

L'invio di una notifica è identico alle app UWP, ad eccezione del fatto che userai DesktopNotificationManagerCompat per creare un avviso PopupNotifier. La libreria di compatibilità gestisce automaticamente la differenza tra le app in pacchetto e non in pacchetto, quindi non è necessario creare una copia tramite fork del codice. Per un'app non in pacchetto, la libreria di compatibilità memorizza nella cache l'AUMID fornito quando si chiama RegisterAumidAndComServer in modo che non sia necessario preoccuparsi di quando fornire o meno l'AUMID.

Assicurarsi di usare l'associazione ToastGeneric come illustrato di seguito, poiché i modelli di notifica di tipo avviso popup di Windows 8.1 legacy non attiveranno l'attivatore di notifica COM creato nel passaggio 4.

Importante

Le immagini HTTP sono supportate solo nelle app in pacchetto con funzionalità Internet nel manifesto. Le app non in pacchetto non supportano immagini HTTP; è necessario scaricare l'immagine nei dati dell'app locale e farvi riferimento in locale.

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

Importante

Le app desktop non possono usare modelli di avviso popup legacy, ad esempio ToastText02. L'attivazione dei modelli legacy avrà esito negativo quando viene specificato IL CLSID COM. È necessario usare i modelli ToastGeneric di Windows, come illustrato in precedenza.

Passaggio 8: Gestione dell'attivazione

Quando l'utente fa clic sull'avviso popup o sui pulsanti nell'avviso popup, viene richiamato il metodo Activate della classe NotificationActivator .

All'interno del metodo Activate è possibile analizzare gli argomenti specificati nell'avviso popup e ottenere l'input dell'utente digitato o selezionato dall'utente e quindi attivare di conseguenza l'app.

Nota

Il metodo Activate viene chiamato su un thread separato dal thread principale.

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

Per supportare correttamente l'avvio mentre l'app è chiusa, nella funzione WinMain è necessario determinare se l'avvio viene eseguito da un avviso popup o meno. Se viene avviato da un avviso popup, verrà visualizzato un argomento di lancio "-ToastActivated". Quando viene visualizzato, è consigliabile interrompere l'esecuzione di qualsiasi codice di attivazione di avvio normale e consentire a NotificationActivator di gestire le finestre di avvio, se necessario.

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

Sequenza di attivazione di eventi

La sequenza di attivazione è la seguente...

Se l'app è già in esecuzione:

  1. Viene chiamato l'attivazione in NotificationActivator

Se l'app non è in esecuzione:

  1. L'app viene avviata con estensione EXE, si ottengono argomenti della riga di comando "-ToastActivated"
  2. Viene chiamato l'attivazione in NotificationActivator

Attivazione in primo piano e in background

Per le app desktop, l'attivazione in primo piano e in background viene gestita in modo identico. Viene chiamato l'attivatore COM. Spetta al codice dell'app decidere se visualizzare una finestra o semplicemente eseguire alcune operazioni e quindi uscire. Pertanto, se si specifica un activationType di sfondo nel contenuto dell'avviso popup, il comportamento non cambia.

Passaggio 9: Rimuovere e gestire le notifiche

La rimozione e la gestione delle notifiche sono identiche alle app UWP. Tuttavia, ti consigliamo di usare la nostra libreria di compatibilità per ottenere un DesktopNotificationHistoryCompat in modo da non doverti preoccupare di fornire l'AUMID per un'app desktop.

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

Passaggio 10: Distribuzione e debug

Per distribuire ed eseguire il debug dell'app in pacchetto, vedere Eseguire, eseguire il debug e testare un'app desktop in pacchetto.

Per distribuire ed eseguire il debug dell'app desktop, è necessario installare l'app tramite il programma di installazione una volta prima del debug normalmente, in modo che sia presente il collegamento Start con AUMID e CLSID. Dopo aver visualizzato il collegamento Start, è possibile eseguire il debug usando F5 da Visual Studio.

Se le notifiche semplicemente non vengono visualizzate nell'app desktop (e non vengono generate eccezioni), ciò significa che il collegamento Start non è presente (installare l'app tramite il programma di installazione) o l'AUMID usato nel codice non corrisponde all'AUMID nel collegamento Start.

Se le notifiche vengono visualizzate ma non vengono mantenute nel Centro notifiche (scomparendo dopo la chiusura del popup), significa che non è stato implementato correttamente l'attivatore COM.

Se hai installato sia l'app desktop in pacchetto che quella non in pacchetto, tieni presente che l'app in pacchetto sostituisce l'app non in pacchetto durante la gestione delle attivazioni di tipo avviso popup. Ciò significa che gli avvisi popup dall'app non in pacchetto avvieranno l'app in pacchetto quando si fa clic su . La disinstallazione dell'app in pacchetto ripristina le attivazioni nell'app non in pacchetto.

Se ricevi HRESULT 0x800401f0 CoInitialize has not been called., assicurati di chiamare nell'app prima di chiamare CoInitialize(nullptr) le API.

Se ricevi HRESULT 0x8000000e A method was called at an unexpected time. durante la chiamata alle API Compat, questo significa che probabilmente non hai chiamato i metodi Register necessari (o se un'app in pacchetto non esegui l'app attualmente in esecuzione nel contesto in pacchetto).

Se si verificano numerosi unresolved external symbol errori di compilazione, è probabile che si sia dimenticato di aggiungere runtimeobject.lib alle dipendenze aggiuntive nel passaggio 1 (oppure è stato aggiunto solo alla configurazione debug e non alla configurazione release).

Gestione delle versioni precedenti di Windows

Se supporti Windows 8.1 o versioni precedenti, dovrai verificare in fase di esecuzione se sei in esecuzione in Windows prima di chiamare qualsiasi API DesktopNotificationManagerCompat o inviare avvisi popup ToastGeneric.

Windows 8 ha introdotto le notifiche di tipo avviso popup, ma ha usato i modelli di avviso popup legacy, ad esempio ToastText01. L'attivazione è stata gestita dall'evento Attivato in memoria nella classe ToastNotification perché gli avvisi popup erano solo brevi popup non persistenti. Windows 10 ha introdotto avvisi popup popup interattivigenerici e ha anche introdotto il Centro notifiche in cui le notifiche vengono mantenute per più giorni. L'introduzione del Centro notifiche richiedeva l'introduzione di un attivatore COM, in modo che l'avviso popup possa essere attivato giorni dopo averlo creato.

Sistema operativo ToastGeneric Attivatore COM Modelli di avviso popup legacy
Windows 10 e versioni successive Supportata Supportata Supportato (ma non attiverà il server COM)
Windows 8.1 / 8 N/D N/D Supportata
Windows 7 e versioni precedenti N/D N/D N/D

Per verificare se è in esecuzione in Windows 10 o versione successiva, includere l'intestazione <VersionHelpers.h> e controllare il metodo IsWindows10OrGreater . Se restituisce true, continuare a chiamare tutti i metodi descritti in questa documentazione.

#include <VersionHelpers.h>

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

Problemi noti

CORREZIONE: l'app non diventa attiva dopo aver fatto clic su avviso popup: nelle build 15063 e precedenti i diritti in primo piano non venivano trasferiti all'applicazione quando è stato attivato il server COM. Di conseguenza, l'app dovrebbe semplicemente lampeggiare quando si tenta di spostarlo in primo piano. Non esiste una soluzione alternativa per questo problema. Questo problema è stato risolto nelle build 16299 o successive.

Risorse