6

C++: Windows Toast Notification

 2 years ago
source link: https://www.codeproject.com/Articles/5286393/Cplusplus-Windows-Toast-Notification
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Introduction

Windows Toast is a small window appearing bottom-right of the screen, is a common method whereby an application notifies its user an event of interest has occurred, for instance, an video encoding session has completed. Compared to MesssageBox() which shows a child modal window of your application is like a shove in your user's face especially when your application is currently not in the foreground. In this regard, Windows Toast is less intrusive. But to understand and use its complex API correctly is not an easy task.

WinToast by Mohammed Boujemaoui is an excellent Windows Toast Notification library that does a good job of hiding the complexity of showing a toast on Windows and most importantly, ease of use.

To get started, copy the wintoastlib.h and wintoastlib.cpp from the WinToast repo to your project and include its header and use the namespace, WinToastLib.

Copy Code
#include "wintoastlib.h"

using namespace WinToastLib;

The second step is to implement the IWinToastHandler interface's toastActivated, toastDismissed and toastFailed virtual methods. The actionIndex parameter of toastActivated method returns the zero-based index of the button clicked.

Copy Code
class WinToastHandler : public WinToastLib::IWinToastHandler
{
public:
    WinToastHandler(CDialogEx* pDialog) : m_pDialog(pDialog) {}
    // Public interfaces
    void toastActivated() const override {}
    void toastActivated(int actionIndex) const override {
        wchar_t buf[250];
        swprintf_s(buf, L"Button clicked: %d", actionIndex);
        m_pDialog->MessageBox(buf, L"info", MB_OK);
    }
    void toastDismissed(WinToastDismissalReason state) const override {}
    void toastFailed() const override {}
private:
    CDialogEx* m_pDialog;
};

Then add the concrete WinToastHandler as a member of your class.

Copy Code
WinToastHandler m_WinToastHandler;

The third step is to configure AppUserModelId (AUMI) which consists of CompanyName, ProductName, SubProductName and VersionInformation. For UWP application, this step can be skipped as the UWP will fill up the information provided by your MSIX installer. For desktop applications like pure Win32 or MFC, this step is essential else the toast notification will fail to work.

Copy Code
WinToast::WinToastError error;
WinToast::instance()->setAppName(L"TestToastExample");
const auto aumi = WinToast::configureAUMI
                  (L"company", L"wintoast", L"wintoastexample", L"20201012");
WinToast::instance()->setAppUserModelId(aumi);

if (!WinToast::instance()->initialize(&error)) {
    wchar_t buf[250];
    swprintf_s(buf, L"Failed to initialize WinToast :%d", error);
    MessageBox(buf, L"Error");
}

Your toast can consist of an image or a number of text lines based on the WinToastTemplateType enum passed to WinToastTemplate constructor. In case you are wondering what hero image is, hero image is the big image that appears at the top of the toast.

Copy Code
enum WinToastTemplateType 
{
    ImageAndText01,
    ImageAndText02,
    ImageAndText03,
    ImageAndText04,
    Text01,
    Text02,
    Text03,
    Text04,
    HeroImageAndImageAndText01,
    HeroImageAndImageAndText02,
    HeroImageAndImageAndText03,
    HeroImageAndImageAndText04,
    HeroImageAndText01,
    HeroImageAndText02,
    HeroImageAndText03,
    HeroImageAndText04,
};

The last step is to show the toast to your user. We chose to show 1 image and 2 lines of text with ImageAndText02. With Windows 10 Anniversary Update, we have the option of adding a hero image that appears on the top or inlined by calling the constructor with HeroImageAndImageAndText02 and setHeroImagePath() with inlineImage set to true or false. setImagePath()'s second parameter can be set to CropHint::Circle to make the picture cropped in circle. This is only available with Windows 10 Anniversary Update. On older unsupported Windows 7/8/10, the picture is still displayed in a square. Two buttons, Yes and No are added through addAction().

Shrink ▲   Copy Code
WinToastTemplate templ;
if (WinToast::isWin10AnniversaryOrHigher())
{
    templ = WinToastTemplate(WinToastTemplate::HeroImageAndImageAndText02);
    bool inlineImage = false;
    templ.setHeroImagePath(L"C:\\Users\\u\\Pictures\\hero.jpg", 
        inlineImage);
}
else
{
    templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
}
templ.setImagePath(
    L"C:\\Users\\u\\Pictures\\pretty_gal.jpg", 
    WinToastTemplate::CropHint::Circle);

templ.setTextField(L"My First Toast", WinToastTemplate::FirstLine);
templ.setTextField(L"Say Hello?", WinToastTemplate::SecondLine);

templ.addAction(L"Yes");
templ.addAction(L"No");

// Read the additional options section in the article
templ.setDuration(WinToastTemplate::Duration::Short);
templ.setAudioOption(WinToastTemplate::AudioOption::Default);
templ.setAudioPath(WinToastTemplate::AudioSystemFile::Call1);

if (WinToast::instance()->showToast(templ, &m_WinToastHandler) == -1L)
{
    MessageBox(L"Could not launch your toast notification!", L"Error");
}

This is the screenshot of the toast notification. Be sure to change the image path and hero image path in the sample code to valid images on your system, else the toast will display a generic icon.

toast screenshot

This is the toast notification with a circle cropped image.

toast screenshot

This is the toast notification with a top hero image. Why my hero image is not a picture of a superhero but scenery?! Actually, I am not aware of the reason Microsoft named the top image, "hero", just like why the toast notification is called "toast". The latter could be due to the similarity to the toast popping up in the toaster when toasted.

toast screenshot with top hero image

This is the toast notification with a big inlined image in the middle.

toast screenshot with a big inlined image in the middle

Additional Options

  • Attribution text: you can add/remove the attribution text, by default is empty. Use WinToastTemplate::setAttributionText to modify it.
  • Duration: The amount of time the toast should display. This attribute can have one of the following values:
    • System: infinite display time until dismissed.
    • Short: default system short time configuration.
    • Long: default system long time configuration.
  • Audio Properties: you can modify the different behaviors of the sound:
    • Default: plays the audio file just one time.
    • Silent: turn off the sound.
    • Loop: plays the given sound in a loop during the toast existence.

WinToast allows the modification of the default audio file. Add the given file in to your projects resources (must be ms-appx:// or ms-appdata:// path) and define it by calling: WinToastTemplate::setAudioPath.

By default, WinToast checks if your systems support the features, ignoring the not supported ones.

Memory Leak Fix

When the article was first released in November 2020, its readers discovered memory leaks on Visual Studio. Using Deleaker, I found a HSTRING leak and callback function token leaks. I fixed the former and for the latter, I keep adding the tokens in _buffer map member because there is no good time to release those tokens. It silences the Visual Studio memory leak detection but the ongoing token accumulation is detrimental to a long running application. The token is used to remove the callback as a subscriber of change notifications when notifications are no longer needed. The crux of the problem is I cannot release the token while on callback due to its memory/resource still being in use. I contemplated using a worker thread to monitor the _buffer but it brings other synchronization and object lifetime problems. Windows Toast Notification is based on COM which has its own threading apartment detail to take care of. The worker thread approach is complex to implement and error-prone. In the callback, it is safe to delete the previous object (that holds the tokens) and marks the current object for next deletion. So I chose this simple approach over the worker thread.

After the fix, the task manager showed the memory of private working set and commit size increased for the first few toast notifications and stabilized afterwards. I have merged my fix with the upstream to do a pull request. Now waiting for original developer, Mohammed Boujemaoui to merge my PR. There is a breaking change in showToast(): handler type is changed from IWinToastHandler raw pointer to IWinToastHandler smart pointer, so update your code accordingly from

Copy Code
if (WinToast::instance()->showToast(templ, &m_WinToastHandler) == -1L)
{...}
Copy Code
std::shared_ptr<IWinToastHandler> handler(new WinToastHandler(this));
if (WinToast::instance()->showToast(templ, handler) == -1L)
{...}

History

  • 15th February, 2022: Fixed the memory leaks permanently. Read this section for information.
  • 4th December, 2021: Uploaded new demo which copies the images to the output directory during post-build event.
  • 28th January, 2021: Added image.zip for those who want to recreate the toast presented in this article. Remember to update the image path in the example code correctly.
  • 8th December, 2020: Added Additional Options section
  • 22th November, 2020: Fixed memory leaks and added hero image and circle cropped image features
  • 16th November, 2020: First release

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK