在现代桌面应用程序中,通知是一种常见的方法,用于告知用户发生了某个事件,例如视频编码会话已完成。与MessageBox()相比,它在应用程序当前不在前台时,就像在用户的脸上推了一把,尤其是当应用程序当前不在前台时。在这方面,Windows Toast通知则不那么侵入性。但是,要正确理解和使用其复杂的API并不容易。
由Mohammed Boujemaoui开发的WinToast是一个出色的Windows Toast通知库,它很好地隐藏了在Windows上显示toast的复杂性,最重要的是,易于使用。
要开始使用,从WinToast库中复制wintoastlib.h和wintoastlib.cpp到项目中,并包含其头文件并使用WinToastLib命名空间。
#include "wintoastlib.h"
using namespace WinToastLib;
第二步是实现IWinToastHandler接口的toastActivated、toastDismissed和toastFailed虚方法。toastActivated方法的actionIndex参数返回被点击按钮的零基索引。
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;
};
第三步是配置AppUserModelId (AUMI),它包括CompanyName、ProductName、SubProductName和VersionInformation。对于UWP应用程序,可以跳过这一步,因为UWP将填写MSIX安装程序提供的信息。对于像纯Win32或MFC这样的桌面应用程序,这一步是必不可少的,否则toast通知将无法工作。
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");
}
toast可以包含一个图像或基于传递给WinToastTemplate构造函数的WinToastTemplateType枚举的多行文本。
enum WinToastTemplateType {
ImageAndText01,
ImageAndText02,
ImageAndText03,
ImageAndText04,
Text01,
Text02,
Text03,
Text04,
};
最后一步是向用户显示toast。选择显示1张图像和2行文本,使用ImageAndText02。在Windows 10周年更新中,可以选择添加一个英雄图像,该图像出现在顶部或内联,通过调用setHeroImagePath()并设置inlineImage为true或false。如果想知道英雄图像是什么,英雄图像是出现在toast顶部的大图像。
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");
}
这是toast通知的截图。确保在示例代码中更改图像路径和英雄图像路径为系统上的有效图像,否则toast将显示一个通用图标。
这是带有圆形裁剪图像的toast通知。
这是带有顶部英雄图像的toast通知。为什么英雄图像不是超级英雄的图片而是风景?!实际上,不知道微软为什么将顶部图像命名为"英雄",就像为什么toast通知被称为"toast"一样。后者可能是因为它与烤面包机弹出的吐司相似。
这是带有中间内联大图像的toast通知。
附加选项: 归属文本:可以通过WinToastTemplate::setAttributionText添加/移除归属文本,默认为空。 持续时间:toast应该显示的时间。此属性可以有以下值之一: 系统:无限显示时间,直到被驳回 短:默认系统短时间配置 长:默认系统长时间配置 音频属性:可以修改声音的不同行为: 默认:只播放一次音频文件 静音:关闭声音 循环:在toast存在期间循环播放给定的声音 WinToast允许修改默认音频文件。将给定文件添加到项目资源中(必须是ms-appx://或ms-appdata://路径),并通过调用WinToastTemplate::setAudioPath来定义。
内存泄漏修复:当文章在2020年11月首次发布时,读者发现Visual Studio存在内存泄漏。使用Deleaker,发现了一个HSTRING泄漏和回调函数令牌泄漏。修复了前者,对于后者,继续将令牌添加到_buffer映射成员中,因为没有合适的时间来释放这些令牌。它消除了Visual Studio内存泄漏检测的声音,但持续的令牌积累对长期运行的应用程序是有害的。令牌用于在不再需要通知时,将回调作为更改通知的订阅者移除。问题的关键是不能在回调时释放令牌,因为它的内存/资源仍在使用中。考虑使用工作线程来监控_buffer,但它带来了其他同步和对象生命周期问题。Windows Toast Notification基于COM,它有自己的线程公寓细节需要处理。工作线程方法实现复杂且容易出错。在回调中,安全地删除前一个对象(持有令牌)并标记当前对象以供下次删除。所以选择了这种简单的方法而不是工作线程。
修复后,任务管理器显示前几个toast通知的私有工作集和提交大小增加了,然后稳定下来。showToast()有一个破坏性变化:处理程序类型从IWinToastHandler原始指针更改为IWinToastHandler智能指针,因此请相应更新代码:
if (WinToast::instance()->showToast(templ, &m_WinToastHandler) == -1L) {...}
std::shared_ptr handler(new WinToastHandler(this));
if (WinToast::instance()->showToast(templ, handler) == -1L) {...}
if (WinToast::instance()->showToast(templ, new WinToastHandler(this)) == -1L) {...}