Delphi制作托盘分析

转自 http://blog.5d.cn/user8/wwle/200905/518768.html

系统状态栏图标是指在Windows桌面系统下边的任务栏右边区域内显示的小图标,通常包括时间和输入法,另外,还会包括一些应用程序,如金山词霸或其它一些杀毒软件等的小图标。通常用鼠标右键点击这些小图标时会弹出菜单,通过选择这些菜单可以灵活地实现程序的各项功能。 
下面本文以一个具体的例子,详细介绍一下利用Delphi实现系统状态栏图标的步骤和方法。 
首先,介绍一下本实例要实现的功能:程序开始运行时会在系统状态栏生成一个小图标,同时会打开应用程序窗口,并在任务栏上显示相应的程序窗口;当用户关闭应用程序窗口时,该窗口和任务栏上相应的应用程序窗口都会消失,但应用程序并没有退出;当用户用鼠标左健单击该图标时,会再次打开应用程序窗口,同时在任务栏上显示应用程序窗口;当用户用鼠标右键单击系统状态栏中应用程序的小图标时,会弹出菜单,选择菜单项“退出”可以完全退出应用程序。 
一、 实现步骤 
1. 创建一个应用程序,在主窗体上增加一个TpopupMenu组件。并为该弹出菜单组件增加菜单项Exit,标题为“退出”。 
2. 在Uses中添加ShellAPI,因为在系统状态栏中增加图标时需调用ShellAPI函数Shell_NotifyIconA。该函数需要2个参数,其中一个是TnotifyIconDataA结构,需在主窗体中增加TnotifyIconDataA类型全局变量ntida。 
3. 定义消息mousemsg,并编写主窗体的mousemessage消息处理函数,此函数说明在图标上用鼠标左键单击时,会打开应用程序窗口;用鼠标右键单击时,会弹出一个菜单。 
下面给出步骤2和3的实现代码: 

unit Unit1; 
interface 
uses 
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Menus, shellapi; 

const 
mousemsg = wm_user + 1; //自定义消息,用于处理用户在图标上点击鼠标的事件 
iid = 100; //用户自定义数值,在TnotifyIconDataA类型全局变量ntida中使用。 

type 
TForm1 = class(TForm) 
...... 
private 
//自定义消息处理函数,处理鼠标点击图标事件 
procedure mousemessage(var message: tmessage); message mousemsg;  
public 
{ Public declarations } 
end; 

var 
Form1: TForm1; 
ntida: TNotifyIcondataA; //用于增加和删除系统状态图标 
implementation 

{$ R *.DFM} 

procedure TForm1.mousemessage(var message: tmessage); 
var 
mousept: TPoint; //鼠标点击位置 
begin 
inherited; 
if message.LParam = wm_rbuttonup then begin //用鼠标右键点击图标 
getcursorpos(mousept); //获取光标位置 
popupmenu1.popup(mousept.x, mousept.y); //在光标位置弹出菜单 
end; 
if message.LParam = wm_lbuttonup then begin //用鼠标左键点击图标 
//显示应用程序窗口 
ShowWindow(Handle, SW_SHOW); 
//在任务栏上显示应用程序窗口 
ShowWindow(Application.handle, SW_SHOW); 
SetWindowLong(Application.Handle, GWL_EXSTYLE, 
not (GetWindowLong(Application.handle, GWL_EXSTYLE) 
or WS_EX_TOOLWINDOW AND NOT WS_EX_APPWINDOW)); 
end; 
message.Result := 0; 
end 

4. 编辑TForm1.FormCreate(Sender: TObject) 
应用程序开始运行时,在系统状态栏上生成图标显示,代码如下: 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
ntida.cbSize := sizeof(tnotifyicondataa); //指定ntida的长度 
ntida.Wnd := handle; //取应用程序主窗体的句柄 
ntida.uID := iid; //用户自定义的一个数值,在uCallbackMessage参数指定的消息中使用 
ntida.uFlags := nif_icon + nif_tip + nif_message;//指定在该结构中uCallbackMessage、hIcon、szTip参数都有效 
ntida.uCallbackMessage := mousemsg;//指定的窗口消息 
ntida.hIcon := Application.Icon.handle;//指定系统状态栏显示应用程序的图标句柄 
ntida.szTip := 'Icon'; //当鼠标停留在系统状态栏该图标上时,出现该提示信息 
shell_notifyicona(NIM_ADD, @ntida); //在系统状态栏增加一个新图标 
end; 

5. 编辑Tform1.OnClose 
当用户关闭应用程序窗口时,该窗口和任务栏上相应的应用程序窗口都消失,但程序并没有退出。代码如下: 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); 
begin 
Action := caNone; //不对窗体进行任何操作 
ShowWindow(Handle, SW_HIDE); //隐藏主窗体 
//隐藏应用程序窗口在任务栏上的显示 
ShowWindow(Application.Handle, SW_HIDE); 
SetWindowLong(Application.Handle, GWL_EXSTYLE, 
GetWindowLong(Application.handle, GWL_EXSTYLE) 
or WS_EX_TOOLWINDOW AND NOT WS_EX_APPWINDOW); 
end; 

6. 编辑弹出菜单Exit 
当用户点击该菜单时完全退出应用程序。代码如下: 
procedure TForm1.ExitClick(Sender: TObject); 
begin 
//为ntida赋值,指定各项参数 
ntida.cbSize := sizeof(tnotifyicondataa); 
ntida.wnd := handle; 
ntida.uID := iid; 
ntida.uFlags := nif_icon + nif_tip + nif_message; 
ntida.uCallbackMessage := mousemsg; 
ntida.hIcon := Application.Icon.handle; 
ntida.szTip := 'Icon'; 
shell_notifyicona(NIM_DELETE, @ntida); //删除已有的应用程序图标 
Application.Terminate; //中断应用程序运行,退出应用程序 
end; 


二、 技术要点 
1.程序中在增加或删除系统状态栏图标时,需调用ShellAPI函数Shell_NotifyIconA,该函数有两个参数,其中一个是TnotifyIconDataA结构,在前面的程序注释中已经对其进行了介绍;另一个参数是dwMessage,通过不同的取值表示是增加图标、修改图标或删除图标。 

2.通过调用一组API函数,实现在任务栏上显示或隐藏应用程序窗口。这些函数分别为ShowWindow、SetWindowLong和GetWindowLong。其中,ShowWindow用于设置指定窗口的显示状态;SetWindowLong和GetWindowLong分别用于改变和检索指定窗体的一个属性。

补充:
如果想一开就最小化到托盘, 加上以下代码

form1.FormShow(Sender: TObject);
begin
postMessage(Application.handle,WM_SYSCOMMAND,SC_CLOSE,0);
end;

并且可以在formcreat的时候把窗口大小设0, 标题栏去掉, 这样基本不会闪现. 但我在恢复显示时候恢复大小没问题, 恢复标题却会造成窗口再次消失, 连托盘图标也没了, 但进程仍在, 只能强行关闭了. 

实践碰到的问题:
托盘右键菜单后, 点其他地方菜单不会自动消失, 搜索到一个说法

GetCursorPos(Point); 
if   IsIconIc(Application.Handle)   then   SetForegroundWindow(Application.Handle) 
else   Application.BringToFront; 
FPopupMenu.Popup(Point.X,Point.Y); 
菜单消失是由其窗口主线程处理的,当你点击托盘图标后,菜单的拥有者也就是菜单的拥有窗口线程就变为后台线程,这时,如果不把窗口切换到前台线程,那么菜单就不会消失! 
上面代码要注意的是,不能只用SetForegroundWindow,否则,在窗口非最小化时菜单不会消失!

但是这个上去后, 也只是在窗口显示出来的情况下, 可以使右键菜单自动消失. 当窗口不显示出来的时候, 托盘菜单还是不能自动消失.  还没搜到或想到其他方法或者变通的解决.

终于找到完美的代码了
popup之前加上 
    SetForegroundWindow(Application.Handle); 
    application.ProcessMessages;
即:

    GetCursorPos(mousept); //获取光标位置
    SetForegroundWindow(Application.Handle); //让右键菜单自动消失
    application.ProcessMessages;             //让右键菜单自动消失
    pm1.Popup(mousept.X,mousept.Y); //在光标位置弹出菜单


还有个问题, 工具栏的右键菜单会变成右对齐, 变成下面即可

    SetWindowLong(Application.Handle, GWL_EXSTYLE,
      not (GetWindowLong(Application.handle, GWL_EXSTYLE)
      or WS_EX_TOOLWINDOW AND NOT WS_EX_APPWINDOW) and WS_EX_LEFT);
即末尾多加上个WS_EX_LEFT, 强制左对齐

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值