Inviare una notifica toast locale da un'app desktop C++ WRL
Le app desktop in pacchetto e non in pacchetto possono inviare notifiche toast interattive allo stesso modo delle app della piattaforma Windows universale (UWP). Che include app in pacchetto (vedere Creare un nuovo progetto per un'app desktop WinUI 3 in pacchetto); app in pacchetto con posizione esterna (vedere Concedere l'identità del pacchetto tramite la creazione di pacchetti con posizione esterna); e app non in pacchetto (vedere 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.
- Aggiungere le dipendenze aggiuntive
runtimeobject.lib
a . - Specificare come destinazione Windows SDK.
Fare clic con il pulsante destro del mouse sul progetto e selezionare Proprietà.
Nel menu Configurazione superiore, selezionare Tutte le Configurazioni in modo che la modifica seguente venga applicata sia a Debug che a Release.
Nella sezione Linker -> Input, aggiungere runtimeobject.lib
alle dipendenze aggiuntive.
Quindi, in Generale, verificare che la versione di Windows SDK sia impostata su 10.0 o successiva.
Passaggio 2: Copiare il codice della libreria di compatibilità
Copia il file DesktopNotificationManagerCompat.h e DesktopNotificationManagerCompat.cpp da GitHub nel tuo progetto. La libreria compat semplifica gran parte della complessità delle notifiche desktop. Le istruzioni seguenti richiedono la libreria compat.
Se utilizzate intestazioni precompilate, assicuratevi di #include "stdafx.h"
come prima riga del file DesktopNotificationManagerCompat.cpp.
Passaggio 3: Includere i file di intestazione e i namespace
Includere il file di intestazione della libreria di compatibilità, insieme ai file di intestazione e agli spazi dei nomi correlati all'uso delle API di notifica toast 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 delle notifiche toast, in modo che quando l'utente fa clic sulla tua notifica toast, la tua app possa eseguire un'azione. Questa operazione è necessaria per rendere persistente la notifica toast nel Centro notifiche (poiché la notifica toast potrebbe essere selezionata giorni dopo la chiusura dell'app). Questa classe può essere inserita in qualsiasi punto del progetto.
Implementare l'interfaccia INotificationActivationCallback come illustrato di seguito, includendo un UUID e chiamare anche CoCreatableClass per contrassegnare la classe come creabile come oggetto 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 COM attivare.
// 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 confezionata o non confezionata. 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).
Confezionato
Se l'app è inserita in un pacchetto (vedi Creare un nuovo progetto per un'app desktop WinUI 3 in pacchetto) o inserita in un pacchetto con posizione esterna (vedere Concedere l'identità del pacchetto tramite creazione di pacchetti con posizione esterna) o se si supportano entrambe, nel Package.appxmanifest aggiungere:
- Dichiarazione per xmlns:com
- Dichiarazione per xmlns:desktop
- Nell'attributo IgnorableNamespaces com e desktop
-
com:Extension per l'attivatore COM usando il GUID del passaggio 4. Assicurati di includere
Arguments="-ToastActivated"
per sapere che il tuo avvio è partito da un notifica popup. - desktop:Extension per windows.toastNotificationActivation dichiarare il CLSID dell'attivatore della notifica 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>
Non confezionato
Se l'app non è impacchettata (vedere Creare un nuovo progetto per un'app desktop WinUI 3 non impacchettata) o se si supportano entrambi, è necessario dichiarare l'ID modello utente applicazione (AUMID) e l'attivatore di tipo avviso popup CLSID (GUID del passaggio 4) sul collegamento dell'app nel menu Start.
Selezionare un AUMID univoco che identificherà l'app. Solitamente si presenta 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 usi WiX per il programma di installazione, modifica il file Product.wxs per aggiungere le due proprietà di scelta rapida al menu Start come illustrato di seguito. Assicurarsi che il GUID del passaggio 4 sia racchiuso in {}
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 di eseguire il debug normalmente, in modo che sia presente un collegamento Start con il tuo 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 fare il 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 sia confezionate che non confezionate, devi registrare il tipo di attivatore di notifica per poter gestire le attivazioni toast.
Nel codice di avvio della tua app, chiama il seguente metodo RegisterActivator. Questa funzione deve essere chiamata affinché tu possa ricevere tutte le attivazioni delle notifiche.
// 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 si userà DesktopNotificationManagerCompat per creare un ToastNotifier. La libreria di compatibilità gestisce automaticamente la differenza tra le app impacchettate e non impacchettate, quindi non è necessario biforcare il tuo codice. Per un'app non in pacchetto, la libreria di compatibilità memorizza nella cache l'AUMID fornito quando è stato chiamato 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 toast legacy di Windows 8.1 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 senza pacchetto non supportano le "immagini HTTP"; è necessario scaricare l'immagine nei dati locali dell'app e farvi riferimento 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(¬ifier);
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 utilizzare modelli di notifiche toast legacy, come 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 nella notifica toast e ottenere l'input 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 avviato da un toast, ci sarà un parametro di avvio "-ToastActivated". Quando viene visualizzato, è consigliabile interrompere l'esecuzione di qualsiasi codice di attivazione di avvio normale e consentire al 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:
- attiva nel tuo NotificationActivator
Se l'app non è in esecuzione:
- L'app viene avviata come un file EXE e si ricevono i parametri della riga di comando "-ToastActivated".
- In NotificationActivator viene chiamato Activate
Attivazione in primo piano e in background
Per le app desktop, l'attivazione in primo piano e in background viene gestita in modo identico, e viene chiamato il tuo attivatore COM. Spetta al codice dell'app decidere se visualizzare una finestra o semplicemente eseguire alcune operazioni e quindi uscire. Pertanto, la specifica di un activationType di in background nel contenuto di tipo avviso popup non modifica il comportamento.
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 di eseguire normalmente il debug, in modo che la scorciatoia Start con il tuo AUMID e CLSID sia presente. 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 la tua app desktop in pacchetto che quella senza pacchetto, tieni presente che l'app in pacchetto avrà la precedenza sull'app senza pacchetto nella gestione delle attivazioni di notifiche toast. 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 CoInitialize(nullptr)
nella tua app prima di chiamare le API.
Se si riceve HRESULT 0x8000000e A method was called at an unexpected time.
durante la chiamata alle API Compat, ciò significa che probabilmente non è stato possibile chiamare i metodi Register necessari (o se un'app in pacchetto non è attualmente in esecuzione nell'ambito del contesto in pacchetto).
Se si verificano numerosi errori di compilazione unresolved external symbol
, è probabile che ci si sia dimenticati di aggiungere runtimeobject.lib
alle dipendenze aggiuntive nel passaggio 1 (oppure che sia stato aggiunto solo alla configurazione Debug e non alla configurazione di Rilascio).
Gestione delle versioni precedenti di Windows
Se supporti Windows 8.1 o una versione precedente, è consigliabile verificare in fase di esecuzione se il sistema operativo in uso è Windows prima di chiamare qualsiasi DesktopNotificationManagerCompat API o inviare notifiche 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 in memoria Activated nella classe ToastNotification poiché i toast erano solo popup brevi che non venivano memorizzati. Windows 10 ha introdotto ToastGeneric interattivie ha introdotto anche il Centro operativo dove le notifiche persistono per più giorni. L'introduzione del Centro notifiche richiedeva l'introduzione di un attivatore COM, così da poter attivare la notifica giorni dopo la sua creazione.
Sistema operativo | ToastGeneric | Attivatore COM | Modelli toast legacy |
---|---|---|---|
Windows 10 e versioni successive | Sostenuto | Sostenuto | Supportato (ma non attiverà il server COM) |
Windows 8.1 / 8 | N/D | N/D | Sostenuto |
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
FIXED: L'app non risulta in primo piano dopo aver fatto clic sulla notifica: nelle build 15063 e precedenti i diritti in primo piano non venivano trasferiti all'applicazione quando il server COM veniva attivato. 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.