Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

C++: Windows Toast Notification

4.96/5 (34 votes)
25 Apr 2023MIT5 min read 75.9K   2.7K  
Windows Toast Notification in C++
In this article, you will learn the steps to use Windows Toast Notification library in C++.

Introduction

Windows Toast is a small window appearing at the bottom-right of the screen, is a common method whereby an application notifies its user an event of interest has occurred, for instance, a 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.

C++
#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.

C++
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;
};

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.

C++
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.

C++
enum WinToastTemplateType 
{
    ImageAndText01,
    ImageAndText02,
    ImageAndText03,
    ImageAndText04,
    Text01,
    Text02,
    Text03,
    Text04,
};

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 setHeroImagePath() with inlineImage set to true or false. In case you are wondering what a hero image is, hero image is the big image that appears at the top of the toast. 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().

C++
WinToastTemplate templ;
templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
if (WinToast::isWin10AnniversaryOrHigher())
{
    bool inlineImage = false;
    templ.setHeroImagePath(L"C:\\Users\\u\\Pictures\\hero.jpg", 
        inlineImage);
}

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, new WinToastHandler(this)) == -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  _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 mark the current object for the next deletion. So I chose this simple approach over the worker thread.

After the fix, the task manager showed the memory of the private working set and the commit size increased for the first few toast notifications and stabilized afterward. 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:

C++
if (WinToast::instance()->showToast(templ, &m_WinToastHandler) == -1L)
{...}

to:

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

In TestToast9 source code, the showToast's second parameter is changed back to raw handler pointer to be consistent with the original repository. Be assured its lifetime is handled by the library, so no memory leak. So showToast() usage is changed to:

C++
if (WinToast::instance()->showToast(templ, new WinToastHandler(this)) == -1L)
{...}

History

  • 25th April, 2023: Please submit your feature requests for the future version.
  • 15th April, 2023: Updated the source code upstream from the original repository after my PR is merged and some refactoring is done by Mohammed Boujemaoui. The breaking change is HeroImageAndImageAndText02 enum value no longer exists. Replace it with ImageAndText02.
  • 3rd July, 2022: Added SAL Annotations to my additions in the source code. Updated this section.
  • 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

License

This article, along with any associated source code and files, is licensed under The MIT License