虽然.net 扩展并平民化了大部分的API,但就notifyicon 来讲,系统自己的notifyicon 做的必然安全,以至于 shell32.dll 新版本(此气泡功能是win98 的ie5 就具备)的功能完全得不到体现,在CSDN 我也见过不少的人都在为扩展一个更完美的notifyicon 做努力,为此笔者就在中秋前夜的一些空闲写了这个扩展控件权砖引玉,这二天就有不少朋友扔鸡蛋,于是特撰此稿来补充此控件的功能及它的具体内容以便于朋友们的二次开发,本文也定义了大部分的一些的常数和结构。
预览图:
组件类特点如下:
- 标准Shell32.dll 的气泡提示,随系统升级
- 支持左、右、中三键点击回调
- 支持右键菜单 (Handle 引用,所以可以自己动态建也可以用.net 自己的ContextMenu)
- 支持动态图标 (Handle 引用,所以可以自己画或资源/外部引用,然后定时改变,用此组件的 ModiNotifyBox 对原有的图标进行修改,它的参数和 AddNotifyBox 一样,只要改变它的icon 句柄为新 icon 的句柄就行了)
- 不使用静态,允许多个Icon 互不冲突 ,可用于多窗体软件
类内容如下:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace ArLi.CommonPrj //要改此名要注意改 InnerClass 里的引用
{
using System.IO;
using System.Diagnostics;
///
1<summary>
2/// Shell_NotifyIconEx Class.
3public class Shell_NotifyIconEx
4{
5/// <summary>
6/// ArLi, last fix: 2003.9.12, reference: ArLi.CommonPrj Lib @ http://zpcity.com/arli/
7/// </summary>
8public static readonly System.Version myVersion = new System.Version(1,2); //版本声明
9
10private readonly InnerClass.FormTmp formTmp = null; // 这个很重要,不能放在构造里,因为它必须和此实例同等生存期才不会被中止消息循环
11private readonly IntPtr formTmpHwnd = IntPtr.Zero; // 这是上一行的句柄
12private readonly bool VersionOk = false; // 这是一个由VersionPass 返回的属性,它允许开发者检测当前机子的Shell32.dll(可能在win95 或未知平台上版本) 合适此组,不符则用.net 自己的notifyicon
13private bool forgetDelNotifyBox = false; // 这是一个私有标志,它允许开发者在程序退出时忘记调用DelNotifyBox 来清除图标时会自动在析构里清掉它。
14
15internal IntPtr formHwnd = IntPtr.Zero; // 这是调用此组件的主窗口句柄(当前实例有效,可多个icon 不冲突)
16internal IntPtr contextMenuHwnd = IntPtr.Zero; // 这是菜单的句柄(当前实例有效,可多个icon 不冲突)
17
18internal delegate void delegateOfCallBack(System.Windows.Forms.MouseButtons mb);
19internal delegateOfCallBack _delegateOfCallBack = null;
20
21public Shell_NotifyIconEx() // 构造
22{
23WM_NOTIFY_TRAY += 1; // 消息ID +1,避免多个ICON 消息处理冲突
24uID += 1; // 同上
25formTmp = new InnerClass.FormTmp(this); // 新实例一个消息循环
26formTmpHwnd = formTmp.Handle; // 新实例句柄
27VersionOk = this.GetShell32VersionInfo() >= 5; // 版本是否合适,此组件由于重点在气泡提示,它要求Shell32.dll 5.0(ie 5.0) 以上
28}
29
30~ Shell_NotifyIconEx() { // 析构
31if (forgetDelNotifyBox) this.DelNotifyBox(); //如果开发者忘记则清理icon
32}
33
34#region API_Consts
35internal readonly int WM_NOTIFY_TRAY = 0x0400 + 2001; //readonly 表示只在构造可付值
36internal readonly int uID = 5000;
37
38// 常数定义,有VC 的可以参见 shellapi.h
39private const int NIIF_NONE = 0x00;
40private const int NIIF_INFO = 0x01;
41private const int NIIF_WARNING = 0x02;
42private const int NIIF_ERROR = 0x03;
43
44private const int NIF_MESSAGE = 0x01;
45private const int NIF_ICON = 0x02;
46private const int NIF_TIP = 0x04;
47private const int NIF_STATE = 0x08;
48private const int NIF_INFO = 0x10;
49
50private const int NIM_ADD = 0x00;
51private const int NIM_MODIFY = 0x01;
52private const int NIM_DELETE = 0x02;
53private const int NIM_SETFOCUS = 0x03;
54private const int NIM_SETVERSION = 0x04;
55
56private const int NIS_HIDDEN = 0x01;
57private const int NIS_SHAREDICON = 0x02;
58
59private const int NOTIFYICON_OLDVERSION = 0x00;
60private const int NOTIFYICON_VERSION = 0x03;
61
62[DllImport("shell32.dll", EntryPoint="Shell_NotifyIcon")]
63private static extern bool Shell_NotifyIcon ( // 这位是主角
64int dwMessage,
65ref NOTIFYICONDATA lpData
66);
67
68/// <summary>
69/// 此API 的作用是当 this.focus() 无效时可以考虑使用,效果很好
70/// </summary>
71/// <param name="hwnd"/>this.Handle, 当前窗体句柄
72[DllImport("user32.dll", EntryPoint="SetForegroundWindow")]
73public static extern int SetForegroundWindow (
74IntPtr hwnd
75);
76
77[StructLayout(LayoutKind.Sequential)]
78private struct NOTIFYICONDATA { // 主角用的结构
79internal int cbSize;
80internal IntPtr hwnd;
81internal int uID;
82internal int uFlags;
83internal int uCallbackMessage;
84internal IntPtr hIcon;
85[MarshalAs(UnmanagedType.ByValTStr, SizeConst=0x80)]
86internal string szTip;
87internal int dwState; // 这里往下几个是 5.0 的精华
88internal int dwStateMask;
89[MarshalAs(UnmanagedType.ByValTStr, SizeConst=0xFF)]
90internal string szInfo;
91internal int uTimeoutAndVersion;
92[MarshalAs(UnmanagedType.ByValTStr, SizeConst=0x40)]
93internal string szInfoTitle;
94internal int dwInfoFlags;
95}
96#endregion
97
98/// <summary>
99/// 建一个结构
100/// </summary>
101private NOTIFYICONDATA GetNOTIFYICONDATA(IntPtr iconHwnd, string sTip, string boxTitle, string boxText) {
102NOTIFYICONDATA nData = new NOTIFYICONDATA();
103
104nData.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(nData); // 结构的大小
105nData.hwnd = formTmpHwnd; // 处理消息循环的窗体句柄,可以移成主窗体
106nData.uID = uID; // 消息的 WParam,回调时用
107nData.uFlags = NIF_MESSAGE|NIF_ICON|NIF_TIP|NIF_INFO; // 标志,表示由消息、图标、提示、信息组成
108nData.uCallbackMessage = WM_NOTIFY_TRAY; // 消息ID,回调用
109nData.hIcon = iconHwnd; // 图标的句柄,有兴趣的话可以定时改变它变成动画ICON
110nData.uTimeoutAndVersion = 10 * 1000 | NOTIFYICON_VERSION; // 提示的超时值(几秒后自动消失)和版本
111nData.dwInfoFlags = NIIF_INFO; // 类型标志,有INFO、WARNING、ERROR,更改此值将影响气泡提示框的图标类型
112
113nData.szTip = sTip; // 图标的提示信息
114nData.szInfoTitle = boxTitle; // 气泡提示框的标题
115nData.szInfo = boxText; // 气泡提示框的提示内容
116
117return nData; // 这个嘛。。。
118}
119
120private int GetShell32VersionInfo() { // 返回shell32 的版本
121FileInfo fi = new FileInfo(Path.Combine(System.Environment.SystemDirectory,"shell32.dll")); //将来的平台shell32 放哪目前不得而知,碰到再改
122if (fi.Exists) {
123FileVersionInfo theVersion = FileVersionInfo.GetVersionInfo(fi.FullName);
124int i = theVersion.FileVersion.IndexOf('.');
125if (i > 0) {
126try {
127return int.Parse(theVersion.FileVersion.Substring(0,i));
128}
129catch{}
130}
131}
132return 0;
133}
134
135/// <summary>
136/// 加一个新图标
137/// </summary>
138/// <param name="iconHwnd"/>图标句柄
139/// <param name="sTip"/>提示, 5.0 最大: 128 char
140/// <param name="boxTitle"/>气泡标题, 最大: 64 char
141/// <param name="boxText"/>气泡内容, 最大: 256 char
142/// <returns>成功、失败或错误(-1)</returns>
143public int AddNotifyBox(IntPtr iconHwnd, string sTip, string boxTitle, string boxText) {
144if (! this.VersionOk) return -1;
145
146NOTIFYICONDATA nData = GetNOTIFYICONDATA(iconHwnd, sTip, boxTitle, boxText);
147if (Shell_NotifyIcon(NIM_ADD,ref nData)) {
148this.forgetDelNotifyBox = true;
149return 1;
150}
151else {
152return 0;
153}
154}
155
156/// <summary>
157/// 和add 差不多,不重复了
158/// </summary>
159public int DelNotifyBox() {
160if (! this.VersionOk) return -1;
161
162NOTIFYICONDATA nData = GetNOTIFYICONDATA(IntPtr.Zero, null, null, null);
163if (Shell_NotifyIcon(NIM_DELETE,ref nData)) {
164this.forgetDelNotifyBox = false;
165return 1;
166}
167else {
168return 0;
169}
170}
171
172public int ModiNotifyBox(IntPtr iconHwnd, string sTip, string boxTitle, string boxText) {
173if (! this.VersionOk) return -1;
174
175NOTIFYICONDATA nData = GetNOTIFYICONDATA(iconHwnd, sTip, boxTitle, boxText);
176return Shell_NotifyIcon(NIM_MODIFY,ref nData) ? 1 : 0;
177}
178
179#region Optional Module //这里是可选方法
180/// <summary>
181/// 连接一个已存在的 contextMenu
182/// </summary>
183/// <param name="_formHwnd"/>窗体句柄,用来处理菜单的消息
184/// <param name="_contextMenuHwnd"/>菜单的句柄
185public void ConnectMyMenu(IntPtr _formHwnd, IntPtr _contextMenuHwnd) {
186formHwnd = _formHwnd;
187contextMenuHwnd = _contextMenuHwnd;
188}
189
190/// <summary>
191/// 立即清理掉图标、委托和formtmp 资源(好象没什么资源,考虑到可能二次开发挂接就开了这个东东)
192/// </summary>
193public void Dispose() {
194_delegateOfCallBack = null;
195this.formTmp.Dispose();
196}
197
198/// <summary>
199/// 版本适合
200/// </summary>
201public bool VersionPass {
202get {
203return this.VersionOk;
204}
205}
206#endregion
207}
208}
209
210#region FormTmp
211namespace ArLi.CommonPrj.InnerClass {
212internal class FormTmp : System.Windows.Forms.Form {
213
214private ArLi.CommonPrj.Shell_NotifyIconEx servicesClass = null; // 接受主CLASS 的实例句柄
215internal FormTmp(ArLi.CommonPrj.Shell_NotifyIconEx _servicesClass) {
216servicesClass = _servicesClass;
217}
218
219private const int WM_LBUTTONDOWN = 0x0201; // 左键
220private const int WM_RBUTTONDOWN = 0x204; // 右键
221private const int WM_MBUTTONDOWN = 0x207; // 中键
222
223[DllImport("user32.dll", EntryPoint="TrackPopupMenu")]
224private static extern int TrackPopupMenu ( // c# 和vb.net 好象没有了随地popup 了,只要请它老人家出马了
225IntPtr hMenu,
226int wFlags,
227int x,
228int y,
229int nReserved,
230IntPtr hwnd,
231ref RECT lprc
232);
233
234[StructLayout(LayoutKind.Sequential)]
235private struct RECT { // 上面那位用的结构,表示前弹出菜单可用的一个范围大小(一般是全屏幕都让它用,留着搞游戏或视频对话之类的朋友指定菜单可用的范围)
236internal int Left;
237internal int Top;
238internal int Right;
239internal int Bottom;
240}
241
242protected override void WndProc(ref Message msg) {
243if (msg.Msg == servicesClass.WM_NOTIFY_TRAY) { // 如果消息相符
244if ((int)msg.WParam == servicesClass.uID) { // 并且消息的WParam 相符
245System.Windows.Forms.MouseButtons mb = System.Windows.Forms.MouseButtons.None;
246if((int)msg.LParam == WM_LBUTTONDOWN) { //如果点击的是左键
247mb = System.Windows.Forms.MouseButtons.Left;
248}
249else if((int)msg.LParam == WM_MBUTTONDOWN) { //中键
250mb = System.Windows.Forms.MouseButtons.Middle;
251}
252else if((int)msg.LParam == WM_RBUTTONDOWN) { //右键
253if (servicesClass.contextMenuHwnd != IntPtr.Zero) { //如果有定义过菜单关联
254RECT r = new RECT();
255r.Left = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Left;
256r.Right = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Right;
257r.Top = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Top;
258r.Bottom = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Right;
259
260TrackPopupMenu(
261servicesClass.contextMenuHwnd,
2622,
263System.Windows.Forms.Cursor.Position.X,
264System.Windows.Forms.Cursor.Position.Y,
2650,
266servicesClass.formHwnd,
267ref r
268);
269}
270else { //如果没有定义过菜单关联
271mb = System.Windows.Forms.MouseButtons.Right;
272}
273}
274
275if (mb != System.Windows.Forms.MouseButtons.None && servicesClass._delegateOfCallBack != null) {
276servicesClass._delegateOfCallBack(mb); // 执行回调
277return;
278}
279}
280}
281
282base.WndProc(ref msg);
283}
284}
285}
286#endregion
287
288用法示例:
289
290 1. 简单示例 :
291private void button2_Click(object sender, System.EventArgs e) {
292new ArLi.CommonPrj.Shell_NotifyIconEx().AddNotifyBox(this.Icon.Handle,this.Text,"zpcity.com/arli","单击这里开始,我将带你畅游API 世界");
293}
294 2. 支持菜单和回调的示例 :
295private void GetPoc1(MouseButtons mb) { // 回调处理
296if (mb == MouseButtons.Left) {
297MessageBox.Show("来自菜单1");
298}
299}
300private ArLi.CommonPrj.Shell_NotifyIconEx o1 = new ArLi.CommonPrj.Shell_NotifyIconEx(); //这个放外面是用在 o.DelNotifyBox
301private void button1_Click(object sender, System.EventArgs e) {
302o1.AddNotifyBox(this.Icon.Handle,this.Text,"菜单1","单击这里开始,我将带你畅游API 世界");
303o1.ConnectMyMenu(this.Handle,this.contextMenu1.Handle); // 挂上菜单,可选
304o1._delegateOfCallBack = new ArLi.CommonPrj.Shell_NotifyIconEx.delegateOfCallBack(GetPoc1); //定义回调
305}
306 3. 多个notifyicon 的高级应用 :
307private void GetPoc1(MouseButtons mb) { // 回调处理
308if (mb == MouseButtons.Left) {
309MessageBox.Show("来自菜单1");
310}
311}
312private ArLi.CommonPrj.Shell_NotifyIconEx o1 = new ArLi.CommonPrj.Shell_NotifyIconEx(); //这个放外面是用在 o.DelNotifyBox
313private void button1_Click(object sender, System.EventArgs e) {
314o1.AddNotifyBox(this.Icon.Handle,this.Text,"菜单1","单击这里开始,我将带你畅游API 世界");
315o1.ConnectMyMenu(this.Handle,this.contextMenu1.Handle); // 挂上菜单,可选
316o1._delegateOfCallBack = new ArLi.CommonPrj.Shell_NotifyIconEx.delegateOfCallBack(GetPoc1); //定义回调
317}
318private void GetPoc2(MouseButtons mb) {
319if (mb == MouseButtons.Left) {
320MessageBox.Show("来自菜单2");
321}
322}
323private ArLi.CommonPrj.Shell_NotifyIconEx o2 = new ArLi.CommonPrj.Shell_NotifyIconEx(); //第二个nofityicon 和上面一样
324private void button2_Click(object sender, System.EventArgs e) {
325o2.AddNotifyBox(this.Icon.Handle,this.Text,"菜单2","单击这里开始,我将带你畅游API 世界");
326o2.ConnectMyMenu(this.Handle,this.contextMenu2.Handle);
327o2._delegateOfCallBack = new ArLi.CommonPrj.Shell_NotifyIconEx.delegateOfCallBack(GetPoc2);
328}
329 4. 编译成DLL 组件给其它语言 :
330csc /t:library /doc:./cls_Shell_NotifyIconEx.xml cls_Shell_NotifyIconEx.cs
331 5. 关于检测版本是否相符 :
332if (! o2.VersionPass) { // 有可能是win98 第一版或95
333//todo: 用.net 自己的notifyicon
334}
335 6. 关于ModiNotifyBox :
336它的用法和 AddNotifyBox 基本一样,它主要用来修改原有的图标,比如 MailCheck 模块要用到的实时多次提示、动画图标等等
337
338
339
340参考文献:
341
342 * Using the Microsoft .NET Compact Framework MessageWindow Class
343http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/messagewindow.asp</summary>