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
.
#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.
class WinToastHandler : public WinToastLib::IWinToastHandler
{
public:
WinToastHandler(CDialogEx* pDialog) : m_pDialog(pDialog) {}
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.
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.
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()
.
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");
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.
This is the toast notification with a circle cropped image.
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.
This is the toast notification 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:
if (WinToast::instance()->showToast(templ, &m_WinToastHandler) == -1L)
{...}
to:
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:
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