分享
 
 
 

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

王朝delphi·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

任务栏(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定义及相关字段意义说明如下所示:

TNotifyIconData50 = record

前7个字段定义与_NOTIFYICONDATAA基本相同

uFlags: UINT;

//uFlags字段增加了如下常数定义

NIF_STATE:dwState、dwStateMask字段有效

NIF_INFO:szInfo、uTimeout、szInfoTitle、

dwInfoFlags字段有效

NIF_GUID:保留值

dwState: DWORD;

//图标状态

NIS_HIDDEN:图标是隐藏的

NIS_SHAREDICON:图标是共享的

dwStateMask: DWORD;

//指明dwState的哪些位可以被读取

如:设置为NIS_HIDDEN则表示图标的隐藏状态可以被读取

szInfo: array[0..255] of

AnsiChar;

//保存气泡提示字符串

uTimeout: UINT;

//气泡提示显示的持续时间

系统默认设置最短10秒,最长30秒

szInfoTitle: array[0..63]

of AnsiChar;

//保存气泡提示标题

dwInfoFlags: DWORD;

//指明是否在气泡提示上显示图标

NIIF_ERROR:“错误”图标

NIIF_INFO:“信息”图标

NIIF_NONE:不显示图标

NIIF_WARNING:“警告”图标

NIIF_ICON_MASK:保留值

NIIF_NOSOUND:不播放音效

end;

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

//{-------------------常数声明----------------------

Const

NIIF_NONE = $00000000;

NIIF_INFO = $00000001;

NIIF_WARNING = $00000002;

NIIF_ERROR = $00000003;

//--------------------------------------------------------------------------}

//{------------------类型声明--------------------

Type

TBalloonTimeout = 10..30; //气泡提示持续时间,单位为秒

TBalloonIconType = ( //气泡提示信息图标控制

bitNone, //不显示图标

bitInfo, //“信息”图标(蓝色)

bitWarning, //“警告”图标(黄色)

bitError); //“错误”图标(红色)

……

end;

//-----------------------------------------------}

//{---------填写公共结构----------------------------

procedure TEoCSysTray.FillDataStructure;

begin

with FIconData do

begin

cbSize := SizeOf(TNotifyIconData50);

wnd := FWindowHandle;

uID := 0;

uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;

//uCallbackMessage、hIcon、szTip三个字段有效

hIcon := FIcon.Handle;

StrPCopy(szTip, FHint);

uCallbackMessage := WM_SYSTRAY;

end;

end; //end of procedure FillDataStructure

//--------------------------------------------------}

//{---------显示气泡提示信息----------------------

function TEoCSysTray.Balloon(Title, Text: string;

IconType: TBalloonIconType; Timeout: TBalloonTimeout): Boolean;

const

aBalloonIconTypes : array[TBalloonIconType] of Byte =

(NIIF_NONE, NIIF_INFO, NIIF_WARNING, NIIF_ERROR);

begin

if fActive then //若通知栏图标处于显示状态

begin //删除原先的气泡提示

FillDataStructure;

with FIconData do

begin

uFlags := uFlags or NIF_INFO; //设置与气泡提示相关的字段有效

StrPCopy(szInfo, ''); //设置提示信息为空,删除气泡提示

end;

Shell_NotifyIcon(NIM_MODIFY, @FIconData);

//以下显示新的气泡提示

FillDataStructure;

with FIconData do

begin

uFlags := uFlags or NIF_INFO;

StrPCopy(szInfo, Text);

uTimeout := Timeout;

StrPCopy(szInfoTitle, Title);

dwInfoFlags := aBalloonIconTypes[IconType];

end {with};

Result := Shell_NotifyIcon(NIM_MODIFY, @FIconData)

end

else

result := True;

end; //end of procedure Balloon

//---------------------------------------------------}

1.2气泡提示的事件通知

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

NIN_BALLOONSHOW

当气泡提示显示后外壳发送此消息

NIN_BALLOONTIMEOUT

当气泡提示由于超时而消失时外壳发送此消息

NIN_BALLOONHIDE

当气泡提示消失时(比如通知栏图标被删除)外壳发送此消息,但气泡提示由于超时而消失不会产生此消息

NIN_BALLOONUSERCLICK

当用户点击鼠标时(点击气泡提示和通知栏图标均可)外壳发送此消息

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

procedure TEoCSysTray.WndProc(var Msg: TMessage);

begin

……

case Msg.LParam of

WM_LBUTTONDOWN:

……

WM_RBUTTONDBLCLk:

……

else if Msg.lParam = NIN_BALLOONSHOW then //气泡提示显示后

begin

if Assigned(FOnBalloonShow) then

FOnBalloonShow(Self)

end

else if Msg.lParam = NIN_BALLOONHIDE then //气泡提示由于超时而消失

begin

if Assigned(FOnBalloonHide) then

FOnBalloonHide(Self)

end

else if Msg.lParam = NIN_BALLOONTIMEOUT then //气泡提示消失

begin

if Assigned(FOnBalloonTimeOut) then

FOnBalloonTimeOut(Self)

end

else if Msg.lParam = NIN_BALLOONUSERCLICK then //用户点击鼠标

begin

if Assigned(FOnBalloonClick) then

FOnBalloonClick(Self)

end

else

Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);

end;

end; //end of procedure WndProc

2Windows发生错误导致外壳Explorer重启时图标的重建

相信很多Windows用户都碰到过这种情况:运行某个程序时出现意外错误,导致外壳程序Explorer.exe崩溃而发生重启(即Explorer.exe被关闭后重新运行),任务栏也在消失后重新生成,但应用程序在通知栏添加的图标消失了,虽然这些程序仍在运行,但再也无法通过通知栏图标与用户交互。为避免这种情况出现,Windows提供了相应的机制。

在安装了Internet Explorer 4.0及以上版本的Windows操作系统中,当任务栏建立后,外壳会向所有顶层的应用程序发出通知消息,该消息是外壳以字符串“TaskbarCreated”为参数向系统注册获得的,应用程序窗口接收到该消息后就应该重新添加的通知栏图标。

在Delphi中实现过程如下:

initialization

MsgTaskbarRestart := RegisterWindowMessage(‘TaskbarCreated’);

1. 重载主窗口的消息处理过程,拦截任务栏重建消息,进行重新添加图标的操作。

procedure TMainForm.WndProc(var Message: TMessage);

begin

……

if Message.Msg = MsgTaskbarRestart then

begin

TrayIcon.Active := False; //删除通知栏图标

TrayIcon.Active := True; //添加通知栏图标

end;

……

inherited WndProc(Message);

end; //end of WndProc

值得一提的是,如果将自动恢复的功能封装为控件,将以后的开发带来方便。但由于外壳只向所有顶层的应用程序发送通知,封装起来有一定的困难。因为通知栏图标的回调函数只能接收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事件中对它进行初始化:

constructor TEoCSysTray.Create(AOwner: TComponent);

begin

……

FMouseDblClicked := False;

FMouseUp := False;

FTimer := TTimer.Create(Self);

with FTimer do

begin

Enabled := False;

Interval := GetDoubleClickTime; //时钟间隔设为双击的时间间隔。

OnTimer := OnButtonTimer; //设置时钟超时响应过程。

end;

……

end; //end of Create

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

procedure TEoCSysTray.WndProc(var Msg: TMessage);

begin

……

case Msg.LParam of

WM_XBUTTONDOWN:

begin

……

FMouseDblClicked := False;//双击尚未发生

FMouseUp := False; //鼠标尚未松开

FTimer.Enabled := False; //结束上次延时

FTimer.Enabled := True; //开始延时

end;

WM_XBUTTONUP:

FMouseUp := True; //设置鼠标已经松开,便于Timer检查

WM_XBUTTONDBLCLk:

begin

FMouseDblClicked := True; //设置双击已经发生的标志

触发双击事件;

end;

else

Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);

end;

end; //end of WndProc

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

procedure TEoCSysTray.OnButtonTimer(Sender: TObject);

begin

FTimer.Enabled := False;

if (not FMouseDblClicked) and FMouseUp then //双击尚未发生且鼠标已松开

begin

触发单击事件;

触发MouseUp事件;

end;

end; //end of procedure OnButtonTimer

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

5总结

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有