Senden von Popupbenachrichtigungen über eine WRL C++-Desktop-App

App-Pakete und nicht gepackte Desktop-Apps können interaktive Popupbenachrichtigungen wie Universal Windows Platform-(UWP-)Apps senden. Dazu gehören App-Pakete (siehe Erstellen eines neuen Projekts für ein WinUI 3-Desktop-App-Paket), App-Pakete mit externem Speicherort (siehe Gewähren der Paketidentität durch Verpacken mit externem Speicherort) und nicht gepackte Apps (siehe Erstellen eines neuen Projekts für eine nicht gepackte WinUI 3-Desktop-App).

Für eine nicht gepackte Desktop-App gibt es jedoch einige spezielle Schritte. Das liegt an den verschiedenen Aktivierungsschemas und an fehlender Paketidentität zur Runtime.

Wichtig

Wenn Sie eine UWP-App schreiben, finden Sie weitere Informationen in der UWP-Dokumentation. Weitere Desktopsprachen finden Sie unter Desktop C#.

Schritt 1: Aktivieren des Windows SDK

Aktivieren Sie zunächst das Windows SDK für Ihre App, falls noch nicht geschehen. Es gibt einige wenige wichtige Schritte.

  1. Fügen Sie runtimeobject.lib den Zusätzlichen Abhängigkeiten hinzu.
  2. Nehmen Sie die Ausrichtung auf das Windows-SDK vor.

Klicken Sie mit der rechten Maustaste auf Ihr Projekt, und wählen Sie Eigenschaften aus.

Wählen Sie im oberen Menü Konfiguration die Option Alle Konfigurationen aus, sodass die folgende Änderung sowohl auf „Debuggen“ als auch auf „Release“ angewendet wird.

Fügen Sie unter Linker –> Eingaberuntimeobject.lib den Zusätzlichen Abhängigkeiten hinzu.

Stellen Sie dann unter Allgemein sicher, dass die Windows SDK-Version auf Version 10.0 oder höher festgelegt ist.

Schritt 2: Kopieren des Compat-Bibliothekscodes

Kopieren Sie die Datei DesktopNotificationManagerCompat.h und DesktopNotificationManagerCompat.cpp aus GitHub in Ihr Projekt. Die compat-Bibliothek abstrahiert einen großen Teil der Komplexität, die mit Desktop-Benachrichtigungen verbunden ist. Für die folgenden Anweisungen ist die Compat-Bibliothek erforderlich.

Wenn Sie vorkompilierte Header verwenden, stellen Sie sicher, dass #include "stdafx.h" als die erste Zeile der DesktopNotificationManagerCompat.cpp Datei verwendet wird.

Schritt 3: Einschließen der Headerdateien und Namespaces

Fügen Sie die Compat-Bibliotheksheaderdatei sowie die Headerdateien und Namespaces ein, die sich auf die Verwendung der Windows-Popup-APIs beziehen.

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

Schritt 4: Implementieren des Aktivators

Sie müssen einen Handler für die Popupaktivierung implementieren. Wenn der Benutzer auf das Popup klickt, kann die App eine Aktion ausführen. Dies ist erforderlich, damit das Popup im Info-Center beibehalten wird (da das Popup auch noch Tage später angeklickt werden könnte, wenn Ihre App geschlossen wird). Diese Klasse kann an einer beliebigen Stelle in Ihrem Projekt platziert werden.

Implementieren Sie die Schnittstelle INotificationActivationCallback wie unten dargestellt, einschließlich einer UUID, und rufen Sie auch CoCreatableClass auf, um Ihre Klasse als COM-erstellungsfähig zu kennzeichnen. Erstellen Sie für Ihre UUID eine eindeutige GUID mit einem der vielen Online-GUID-Generatoren. Mithilfe dieser GUID CLSID (Klassenbezeichner) erkennt das Info-Center, welche Klasse COM-aktiviert werden soll.

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

Schritt 5: Registrieren bei der Benachrichtigungsplattform

Anschließend müssen Sie sich bei der Benachrichtigungsplattform registrieren. Es gibt unterschiedliche Schritte, je nachdem, ob Ihre App ein App-Paket oder nicht gepackt ist. Wenn Sie beides unterstützen, müssen Sie beide Schrittsätze ausführen (es ist jedoch nicht erforderlich, den Code zu verzweigen, da die Bibliothek dies für Sie übernimmt).

Enthalten

Wenn Ihre App gepackt (siehe Erstellen eines neuen Projekts für eine gepackte WinUI 3-Desktop-App) oder gepackt mit externem Speicherort (siehe Erteilen der Paketidentität durch Packen mit externem Speicherort) ist, oder wenn Sie beides unterstützen, fügen Sie in Ihrem Package.appxmanifest Folgendes hinzu:

  1. Deklaration für xmlns:com
  2. Deklaration für xmlns:desktop
  3. Im Attribut IgnorableNamespaces, com und Desktop
  4. com:Extension für den COM-Aktivator mithilfe der GUID aus Schritt 4. Achten Sie darauf, Arguments="-ToastActivated" einzuschließen, sodass Sie wissen, dass der Start aus einem Popup erfolgte.
  5. desktop:Extension für windows.toastNotificationActivation to declare your toast activator CLSID (the GUID from step #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>

Unverpackt

Wenn Ihre App nicht gepackt ist (siehe Erstellen eines neuen Projekts für eine nicht gepackte WinUI 3-Desktop-App) oder wenn Sie beides unterstützen, müssen Sie die Anwendungsbenutzermodell-ID (AUMID) und die Popupaktivator-CLSID (die GUID aus Schritt 4) für die Verknüpfung Ihrer App im Startmenü deklarieren.

Wählen Sie eine eindeutige AUMID aus, die Ihre App identifiziert. Dies hat in der Regel das folgende Format: [CompanyName].[AppName]. Sie müssen jedoch sicherstellen, dass sie in allen Apps einzigartig ist (fügen Sie beispielsweise einige Ziffern am Ende hinzu).

Schritt 5.1: WiX-Installationsprogramm

Wenn Sie WiX als Installationsprogramm verwenden, bearbeiten Sie die Datei Product.wxs, um die beiden Verknüpfungseigenschaften Ihrer Startmenü-Verknüpfung hinzuzufügen, wie unten dargestellt. Achten Sie darauf, dass Ihre GUID aus Schritt 4 wie {} unten dargestellt eingeschlossen ist.

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>

Wichtig

Um Benachrichtigungen tatsächlich verwenden zu können, müssen Sie Ihre App einmal vor dem Debuggen über das Installationsprogramm installieren, damit die Start-Verknüpfung mit Ihrer AUMID und CLSID vorhanden ist. Wenn die Start-Verknüpfung vorhanden ist, können Sie mit F5 in Visual Studio debuggen.

Schritt 5.2: Registrieren des AUMID- und COM-Servers

Rufen Sie dann unabhängig vom Installationsprogramm im Startup-Code Ihrer App (vor dem Aufrufen der Benachrichtigungs-APIs) die RegisterAumidAndComServer-Methode auf, und geben Sie ihre Benachrichtigungsaktivatorklasse aus Schritt 4 und Ihre oben verwendete AUMID an.

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

Wenn Ihre App sowohl gepackte als auch nicht gepackt Bereitstellungen unterstützt, können Sie diese Methode unabhängig davon aufrufen. Wenn Sie App-Pakete ausführen (d. h. mit Paketidentität zur Runtime), wird diese Methode einfach sofort zurückgegeben. Es ist nicht erforderlich, den Code zu verzweigen.

Mit dieser Methode können Sie die compat-APIs aufrufen, um Benachrichtigungen zu senden und zu verwalten, ohne ihre AUMID ständig bereitstellen zu müssen. Außerdem wird der LocalServer32-Registrierungsschlüssel für den COM-Server eingefügt.

Schritt 6: Registrieren des COM-Aktivators

Für gepackte und nicht gepackte Apps müssen Sie den Benachrichtigungsaktivatortyp registrieren, damit Sie Popupaktivierungen verarbeiten können.

Rufen Sie im Startup-Code Ihrer App die Methode RegisterActivator auf: Dies muss aufgerufen werden, damit Sie Popupaktivierungen erhalten können.

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

Schritt 7: Senden einer Benachrichtigung

Das Senden einer Benachrichtigung ist identisch mit UWP-Apps, mit der Ausnahme, dass Sie DesktopNotificationManagerCompat verwenden, um einen ToastNotifier zu erstellen. Die Compat-Bibliothek behandelt automatisch den Unterschied zwischen gepackten und nicht gepackten Apps, sodass Sie den Code nicht verzweigen müssen. Bei einer gepackten App wird AUMID von der compat-Bibliothek die AUMID, die Sie beim Aufrufen von RegisterAumidAndComServer angegeben haben, zwischengespeichert, sodass Sie sich keine Gedanken darüber machen müssen, wann Sie die AUMID bereitstellen oder nicht bereitstellen.

Stellen Sie sicher, dass Sie die ToastGeneric-Bindung wie unten dargestellt verwenden, da die älteren Windows 8.1-Popupbenachrichtigungsvorlagen den in Schritt 4 erstellten COM-Benachrichtigungsaktivator nicht aktivieren.

Wichtig

HTTP-Bilder werden nur in gepackten Apps unterstützt, die eine Internetfunktion in ihrem Manifest haben. Gepackte Apps unterstützen keine HTTP-Bilder; Sie müssen das Bild in Ihre lokalen App-Daten herunterladen und lokal darauf verweisen.

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

Wichtig

Desktop-Apps können keine Legacy-Popupvorlagen (z. B. ToastText02) verwenden. Die Aktivierung der Legacyvorlagen schlägt fehl, wenn die COM-CLSID angegeben wird. Sie müssen die Windows ToastGeneric-Vorlagen wie oben gezeigt verwenden.

Schritt 8: Behandeln der Aktivierung

Wenn der Benutzer auf Ihr Popup oder auf die Popup-Schaltflächen klickt, wird die Activate-Methode Ihrer NotificationActivator-Klasse aufgerufen.

Innerhalb der Activate-Methode können Sie die im Popup angegebenen Argumente analysieren und die Benutzereingabe abrufen, die der Benutzer eingegeben oder ausgewählt hat, und dann die App entsprechend aktivieren.

Hinweis

Die Methode Activate wird für einen separaten Thread aus Ihrem Standardthread aufgerufen.

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

Um den Start ordnungsgemäß zu unterstützen, wenn die App geschlossen ist, sollten Sie in der WinMain-Funktion ermitteln, ob Sie den Start über ein Popup initiieren oder nicht. Falls der Start von einem Popup erfolgt, gibt es das Startargument „-ToastActivated“. Wenn dies angezeigt wird, beenden Sie die Ausführung eines normalen Startaktivierungscodes und Ihrem NotificationActivator erlauben, das Starten von Fenstern bei Bedarf zu behandeln.

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

Aktivierungssequenz von Ereignissen

Die Aktivierungssequenz sieht wie folgt aus ...

Wenn Ihre App bereits ausgeführt wird:

  1. Aktivieren in Ihrem NotificationActivator wird aufgerufen

Wenn Ihre App nicht ausgeführt wird:

  1. Ihre App wird via EXE gestartet, Sie erhalten das Befehlszeilenargumente „-ToastActivated“
  2. Aktivieren in Ihrem NotificationActivator wird aufgerufen

Vordergrund- und Hintergrundaktivierung

Bei Desktop-Apps wird die Vordergrund- und Hintergrundaktivierung identisch behandelt – Ihr COM-Aktivator wird aufgerufen. Ihr App-Code entscheidet, ob ein Fenster angezeigt oder einfach einige Aufgaben ausgeführt und dann beendet werden soll. Daher ändert sich das Verhalten beim Angeben von activationTypeHintergrund in Popupinhalten nicht.

Schritt 9: Benachrichtigungen entfernen und verwalten

Das Entfernen und Verwalten von Benachrichtigungen ist identisch mit UWP-Apps. Es wird jedoch empfohlen, unsere Compat-Bibliothek zum Abrufen von DesktopNotificationHistoryCompat zu verwenden, damit Sie sich keine Gedanken über die Bereitstellung der AUMID für eine Desktop-App machen müssen.

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

Schritt 10: Bereitstellen und Debuggen

Informationen zum Bereitstellen und Debuggen Ihrer gepackten App finden Sie unter Ausführen, Debuggen und Testen einer gepackten Desktop-App.

Um Ihre Desktop-App bereitzustellen und zu debuggen, müssen Sie Ihre App einmal vor dem Debuggen über das Installationsprogramm installieren, damit die Startverknüpfung mit Ihrer AUMID und CLSID vorhanden ist. Wenn die Start-Verknüpfung vorhanden ist, können Sie mit F5 in Visual Studio debuggen.

Wenn die Benachrichtigungen in Ihrer Desktop-App einfach nicht angezeigt werden (und keine Ausnahmen ausgelöst werden), liegt dies möglicherweise daran, dass die Startverknüpfung nicht vorhanden ist (installieren Sie Ihre App über das Installationsprogramm), oder die im Code verwendete AUMID nicht mit der AUMID in Ihrer Startverknüpfung übereinstimmt.

Wenn die Benachrichtigungen im Info-Center angezeigt, aber nicht beibehalten werden (ausgeblendet, nachdem das Popup geschlossen wurde), bedeutet dies, dass Sie den COM-Aktivator nicht ordnungsgemäß implementiert haben.

Wenn Sie sowohl Ihre gepackte als auch die nicht gepackte Desktop-App installiert haben, beachten Sie, dass die gepackte App beim Behandeln von Popupaktivierungen die nicht gepackte App ersetzt. Das bedeutet, dass Popups aus der gepackten App beim Klicken die gepackte App starten. Durch die Deinstallation der gepackten App werden die Aktivierungen zurückgesetzt und die App kehrt in den nicht gepackten Zustand zurück.

Wenn Sie HRESULT 0x800401f0 CoInitialize has not been called. empfangen, müssen Sie vor dem Aufrufen der APIs unbedingt CoInitialize(nullptr) in Ihrer App aufrufen.

Wenn Sie beim Aufrufen der Compat-APIs HRESULT 0x8000000e A method was called at an unexpected time. empfangen, bedeutet dies wahrscheinlich, dass Sie die erforderlichen Registrierungsmethoden nicht aufrufen konnten (oder Sie – falls es sich um eine gepackte App handelt – derzeit Ihre App nicht als gepackt ausführen).

Wenn Sie mehrere unresolved external symbol-Kompilierungsfehler erhalten, haben Sie vermutlich vergessen, runtimeobject.lib den Zusätzlichen Abhängigkeiten im Schritt 1 hinzuzufügen (oder Sie haben sie nur der Debugkonfiguration hinzugefügt und keine Releasekonfiguration).

Behandeln älterer Versionen von Windows

Wenn Sie Windows 8.1 oder niedriger unterstützen, sollten Sie zur Runtime überprüfen, ob die Ausführung unter Windows erfolgt, bevor Sie DesktopNotificationManagerCompat-APIs aufrufen oder ToastGeneric-Popups senden.

Windows 8 hat Popupbenachrichtigungen eingeführt, aber die legacy-Popupvorlagen wie ToastText01 verwendet. Die Aktivierung wurde vom In-Memory-Ereignis Activated der ToastNotification-Klasse behandelt, da die Popups nur kurze Popups waren, die nicht beibehalten wurden. In Windows 10 wurden interaktive ToastGeneric-Popups und außerdem das Info-Center eingeführt, in dem Benachrichtigungen mehrere Tage lang beibehalten werden. Die Einführung des Info-Centers erforderte die Einführung eines COM-Aktivators, damit Ihr Popup auch noch Tage nach der Erstellung aktiviert werden kann.

Betriebssystem ToastGeneric COM-Aktivator Legacy-Popupvorlagen
Windows 10 und höher Unterstützt Unterstützt Unterstützt (der COM-Server jedoch nicht aktiviert)
Windows 8.1/8 Unterstützt
Windows 7 und niedriger

Um zu überprüfen, ob die Ausführung unter Windows 10 oder höher erfolgt, schließen Sie den <VersionHelpers.h>-Header ein, und überprüfen Sie die IsWindows10OrGreater-Methode. Wenn true zurückgegeben wird, rufen Sie weiterhin alle in dieser Dokumentation beschriebenen Methoden auf.

#include <VersionHelpers.h>

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

Bekannte Probleme

BEHOBEN: Die App wird nach dem Klicken auf Popups nicht fokussiert: In Builds 15063 und früher wurden Vordergrundberechtigungen nicht auf Ihre Anwendung übertragen, wenn der COM-Server aktiviert wurde. Daher würde Ihre App einfach blinken, wenn Sie versuchen, sie in den Vordergrund zu verschieben. Für dieses Szenario gab es keine Problemumgehung. Der Fehler wurde in den Builds 16299 oder höher behoben.

Ressourcen