Windows通知栏图标高级编程概述

 Windows通知栏图标高级编程概述
任务栏(Taskbar)是微软公司在Windows 95 中引入的一种特殊的桌面工具条,它为用户快速访问计算机资源提供了极大的方便,而状态栏(以下称通知栏)无疑是任务栏上较为特殊的一个窗口。编程人员可以 调用API函数Shell_NotifyIcon向通知栏发送消息来添加、删除或修改图标,当在图标上发生鼠标或键盘事件时,系统会向应用程序发送编程时 预先定义的消息,通知栏处理回调函数就会被自动调用以做出相应的处理。实现上述功能的相关文章俯仰即拾,此处不再赘述。本文将讨论通知栏编程中几个较为深 入的问题及其在Delphi中的实现方法。


l 新版Windows操作系统引入的卡通风格的气泡提示(Balloon ToolTips)的实现及相关事件通知
l 外壳Explorer.exe崩溃而重启后通知栏图标的自动恢复
l 为通知栏图标快捷菜单选择适当的弹出时机
l 鼠标双击事件发生时单击事件的避免

1 气泡提示(Balloon ToolTips)的实现
1.1  显示气泡提示
我们知道,Shell_NotifyIcon函数需要传入指向某个特定结构的指针,系统根据该结构所包含的信息来决定是向通知栏添加、删除或修改图标。该结构的传统定义如下所示:

_NOTIFYICONDATAA = record
cbSize: DWORD; //该结构的大小
Wnd: HWND; //接收通知消息的窗口句柄
uID: UINT; //图标标识(可以添加多个图标)
uFlags: UINT; //指明该结构中哪些字段的值有效
uCallbackMessage: UINT; //程序定义的接收通知的回调消息
hIcon: HICON; //图标句柄
szTip: array [0..63] of AnsiChar; //鼠标经过图标时显示的提示信息
end;


点击在新窗口中浏览此图片
 气泡提示(Balloon ToolTips)(如图1)是装有Internet Explorer 5 及以上版本浏览器的操作系统(Windows Me /2000/XP,不包括Windows9x)中引入的通知栏图标的新行为,同时系统也定义了新版本的NOTIFYICONDATA结构,用于支持气泡提 示。本文中将新结构取名为TNotifyIconData50,其Object Pascal定义及相关字段意义说明如下所示:

以下代码演示了在Delphi中如何实现气泡提示。

1.2  气泡提示的事件通知
 由于新风格提示的引入,通知栏图标的消息通知也相应增加,如果通知栏图标实现了气泡提示,那么当用户将鼠标指针移动到通知栏图标上时,Windows外壳会向通知栏应用程序送出如下四个消息中的一个或多个。

NIN_BALLOONSHOW 当气泡提示显示后外壳发送此消息
NIN_BALLOONTIMEOUT 当气泡提示由于超时而消失时外壳发送此消息
NIN_BALLOONHIDE 当气泡提示消失时(比如通知栏图标被删除)外壳发送此消息,但气泡提示由于超时而消失不会产生此消息
NIN_BALLOONUSERCLICK 当用户点击鼠标时(点击气泡提示和通知栏图标均可)外壳发送此消息



在Delphi强大的消息封装机制支持下,可以方便地将上述四个消息封装为四个事件供开发人员使用。简单来说就是在控件中一个隐藏窗口(创建隐藏窗口的方法可查阅相关文章,此处略过)的窗口消息处理过程中接收这四个消息并分别映射到四个事件,示范代码如下:

2  Windows发生错误导致外壳Explorer重启时图标的重建
 相信很多Windows用户都碰到过这种情况:运行某个程序时出现意外错误,导致外壳程序Explorer.exe崩溃而发生重启(即 Explorer.exe被关闭后重新运行),任务栏也在消失后重新生成,但应用程序在通知栏添加的图标消失了,虽然这些程序仍在运行,但再也无法通过通 知栏图标与用户交互。为避免这种情况出现,Windows提供了相应的机制。
 在安装了Internet Explorer 4.0
   及以上版本的Windows操作系统中,当任务栏建立后,外壳会向所有顶层的应用程序发出通知消息,该消息是外壳以字符串“
TaskbarCreated”
为参数向系统注册获得的,应用程序窗口接收到该消息后就应该重新添加的通知栏图标。
 在Delphi中实现过程如下:

值得一提的是,如果将自动恢复的功能封装为控件,将以后的开发带来方便。但由于外壳只向所有顶层的应用程序发送通知,封装起来有一定的困难。因为通知栏图 标的回调函数只能接收WM_XBUTTONDOWN、WM_XBUTTONUP等有限的几个消息,并不能接收所有的窗口消息。
 解决的方法是使用SetWindowLong函数。通过向它传入GWL_WNDPROC参数,可以改变一个窗口的窗口过程。只需在创建控件时将应用程序 窗口的窗口过程指针保存起来,并指向为控件中的某个新的窗口处理过程,在控件中就能够响应所有的窗口消息了(包括任务栏重建的消息);当控件销毁的时候再 将保存的原始窗口过程指针恢复即可,此处不再赘述。

3  与通知栏图标关联的快捷菜单弹出的时机
 本节将讨论编写通知栏应用程序时应该注意的一个问题,即快捷菜单弹出的时机问题。Windows为通知栏图标提供了几个鼠标消息(事件),那么我们应该将弹出快捷菜单的代码写在哪个事件中呢?先别急于回答“
放在MouseDown事件中”
,事实上,这个看似简单的问题,其中却小有讲究。许多软件(有的甚至号称专业级软件)也都或多或少忽视了这个问题。
 首先需要明确一个软件设计中通用的原则,即:应当给用户一个机会以确认是否执行他选择的操作。这在软件设计中有很多例子。大的方面,最普遍的,如用户选 择了删除文件,应弹出窗口予以确认。小的方面,如Windows中对鼠标的常规处理,也有一个确认的动作。一般来说,Windows中的程序对于鼠标事件 的响应都是这样:在用户松开鼠标后才认为他确认了点击操作。以按钮(Button)为例,对于Windows的标准按钮,用户都可以在按下鼠标后而未松开 鼠标前把鼠标移动到按钮区域以外来取消这次单击操作。再如Windows中窗口系统菜单的弹出,当用户在窗口标题栏上按下鼠标右键后,可以把鼠标移动到标 题栏以外再松开,这样系统菜单就不会弹出,即等价于用户取消了该次操作。
 遵照这个原则,通知栏快捷菜单的弹出显然应该在用户松开鼠标按键后,即WM_XBUTTONUP消息到来时才发生,以保证用户能够在松开鼠标之前取消其 弹出,而不应简单的把弹出菜单的代码放在WM_XBUTTONDOWN的消息响应中。纵观Windows操作系统附带的程序,皆是如此。

4  鼠标双击事件发生时单击事件的避免
 编写过通知栏应用程序的朋友大概都碰到过这样的情况:如果编写了响应鼠标单击(WM_XBUTTONUP)与双击(WM_XBUTTONDBLCLK) 的代码,那么在用户双击鼠标时单击事件也会发生。而在实际应用中通常希望单击与双击是相互独立的两个操作,它们之间不应该互相影响。对于这一问题,有些软 件采用“
鸵鸟战术”
,不响应单击事件(即对WM_XBUTTONUP消息不作响应),只响应双击事件,这未尝不是一种解决办法,但浪费了单击事件,算不得好。通过下面的分析,我们将会看到一个较为令人满意的解决方法。

4.1  原理分析
 在Windows中并没有定义表示鼠标单击的消息,单击事件在Delphi等可视化编程语言中定义为鼠标按下后松开,因而单击事件一般在 WM_XBUTTONUP中触发。而双击事件则不同,它在Windows中有明确的定义,当用户双击任意一个鼠标按键时,实际上按如下顺序Windows 送出了四次消息:WM_XBUTTONDOWN、WM_XBUTTONUP、WM_XBUTTONDBLCLK、WM_XBUTTONUP。显然,如果响 应WM_XBUTTONUP消息而触发了单击事件,那么双击时必然会先触发一次单击。
 我们的目的是对双击事件单独处理,为此只需引入一个延时机制即可。让计时器在发生WM_XBUTTONDOWN时开始计时,待超时后检查 WM_XBUTTONDBLCLK是否已经发生,若已发生则触发双击事件,否则触发单击事件。关键的是延时多久才合适呢?长了没有意义,短了可能超时后 WM_XBUTTONDBLCLK都没有发生。显然应该至少延迟双击时两次单击之间的时间间隔,这一时间可以有系统API函数 GetDoubleClickTime得到。

4.2  解决方案
 按照如下几个步骤对通知栏图标控件的代码稍加修改即可(注意WM_XBUTTONUP等消息中的"X"可为"L"、"M"、"B",表示鼠标左键、中键、右键)。
 A.定义两个变量FMouseDblClicked和FMouseUp,分别用以指示双击和鼠标松开是否已经发生,均初始化为False。
 B.再为TEoCTrayIcon控件添加一个TTimer类成员变量FTimer,并在OnCreate事件中对它进行初始化:

C.接下来在前述重载的隐藏窗口消息处理过程中响应不同消息来设置上述两个变量的状态。

D.在延时处理程序中判断鼠标状态,触发单击事件。

如此一来,单击事件就表现为WM_XBUTTONDOWN, Click,
 WM_XBUTTONUP,而双击事件则表现为WM_XBUTTONDOWN, WM_XBUTTONDBLCLK(过滤掉了两条MW_XBUTTONUP消息),从而避免了双击事件发生时触发单击事件。

5  总结
 关于通知栏图标的编程还有很多话题,比如动态切换图标、响应MouseLeave和MouseEnter事件等,在实际中都有应用,难以面面俱到。

原文地址:https://www.cnblogs.com/xieyunc/p/2793737.html