这篇日志的标题其实应该起《为啥我的气泡提示无效》,不过如果你看过《Windows编程启示录》中那篇《没错,我们实现了这个功能》短文就大致可以理解为啥我取了这么个标题
周末被叫去加班,自己手头的事已基本已经完结,于是蛋疼地帮忙调查下托盘气泡提示在XP下无法显示的原因。那段托盘相关的代码是从闪电邮原先的代码中挪过来,对NOTIFYICONDATA进行了一层包装。撇开那些无谓的封装,一个简单的更新气泡提示方法应该是这样的:
NOTIFYICONDATA data;
memset(&data,0,sizeof(data));
data.cbSize = sizeof(NOTIFYICONDATA);
data.hWnd = hwnd; //继承类窗口句柄
data.uFlags |= NIF_INFO;
data.szInfoTitle = _T("I'm a Title");
data.szInfo = _T("I'm the Content");
data.dwInfoFlags = NIIF_INFO;
data.uTimeout = 10000;
data.uID = 1;
data.uCallbackMessage = WM_USER + 0x1010; //自定义消息
data.hIcon = hIcon;
Shell_NotifyIcon(NIM_MODIFY, &data);
整个方法很简单,无非就是填充一个NOTIFYICONDATA的结构体信息,并调用Shell_NotifyIcon而已。但是就是这么简单的代码在XP下就可耻地失败了(函数返回TRUE,却没有任何显示),反倒在Win7下可以正常执行。通过查看NOTIFYICONDATA结构体的定义可以知道,其定义在不同系统下是是不一样的:
typedef struct _NOTIFYICONDATAW
{
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
#if (NTDDI_VERSION < NTDDI_WIN2K)
WCHAR szTip[64];
#endif
#if (NTDDI_VERSION >= NTDDI_WIN2K)
WCHAR szTip[128];
DWORD dwState;
DWORD dwStateMask;
WCHAR szInfo[256];
union {
UINT uTimeout;
UINT uVersion; // used with NIM_SETVERSION, values 0, 3 and 4
} DUMMYUNIONNAME;
WCHAR szInfoTitle[64];
DWORD dwInfoFlags;
#endif
#if (NTDDI_VERSION >= NTDDI_WINXP)
GUID guidItem;
#endif
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
HICON hBalloonIcon;
#endif
} NOTIFYICONDATAW, *PNOTIFYICONDATAW;
这样就可以推测实际上这个方法在不同系统上是有不同的定义和表现的,只有在满足其要求才可能调用成功。google了一把,果然如此:如果当前系统低于程序编译时制定的平台定义,这个函数虽然会成功实际上却是做了nothing。坑爹呢,为啥返回TRUE,为啥连个LastError都木有……
正确的做法就是修改编译时指定的NTDDI_VERSION,即_WIN32_WINNT。当然也有人指出需要改WINVER,比如这篇文章:《I can’t get a Balloon Tooltip to work 》。于是索性全改了:以前的工程配置里面指定的_WIN32_WINNT竟然是0×0600,难怪只有我的WIN7系统能够出气泡。至于坑爹的Shell_NotifyIcon函数内部到底做了什么判断就只能问微软了。这让我想起了JUCE里面有个URL类:它有个类方法叫做isWellFormed()用于判断传入的URL是否有效,当时看到这方法觉得很有意思,跳进去看源代码,结果就悲剧了:
bool URL::isWellFormed() const
{
//xxx TODO
return url.isNotEmpty();
}
不过怎么说呢,至少JUCE有机会让我们知道他是怎么坑爹的,Shell_NotifyIcon就只能靠猜了。