c#的任務欄托盤圖標控件NotifyIcon只有MouseMove事件,MouseMove事件刷新很快,很不好用,而且我們有時需要鼠標進入和離開的事件,但是不知道c#怎么回事,沒有提供,那么就只能自己來處理了。
解決鼠標進入和離開的思路是:
1.通過MouseMove事件確定當前鼠標已經進入托盤圖標的范圍
2.進入后啟動檢測timer
3.定時檢測托盤圖標的位置和當前鼠標的位置,判斷鼠標是否在托盤圖標的范圍內
主要難點:獲取當前托盤圖標的位置
獲取托盤圖標位置的思路:
1.查找到托盤圖標所在的窗口

private IntPtr FindTrayToolbarWindow() { IntPtr hWnd = FindWindow("Shell_TrayWnd", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "TrayNotifyWnd", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "SysPager", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null); } } } return hWnd; }
2.遍歷窗口內的托盤圖標
3.獲取當前托盤圖標的句柄,通過句柄得到這個托盤圖標所關聯的進程id
4.通過進程id比較獲取到當前程序的托盤圖標
5.拖過api獲取當前托盤圖標相對於它所在窗口的位置
6.獲取窗口在整個屏幕中的位置,在計算出托盤圖標相對於屏幕的位置
2-6代碼:

private bool FindNotifyIcon(IntPtr hTrayWnd, ref Rect rectNotify) { UInt32 trayPid = 0; Rect rectTray = new Rect(); GetWindowRect(hTrayWnd, out rectTray); int count = (int) SendMessage(hTrayWnd, TB_BUTTONCOUNT, 0, IntPtr.Zero); //給托盤窗口發消息,得到托盤里圖標 bool isFind = false; if (count > 0) { GetWindowThreadProcessId(hTrayWnd, out trayPid); //取得托盤窗口對應的進程id //獲取托盤圖標的位置 IntPtr hProcess = OpenProcess(ProcessAccess.VMOperation | ProcessAccess.VMRead | ProcessAccess.VMWrite, false, trayPid); //打開進程,取得進程句柄 IntPtr address = VirtualAllocEx(hProcess, //在目標進程中申請一塊內存,放TBBUTTON信息 IntPtr.Zero, 1024, AllocationType.Commit, MemoryProtection.ReadWrite); TBBUTTON btnData = new TBBUTTON(); TRAYDATA trayData = new TRAYDATA(); // Console.WriteLine("Count:"+count); var handel = Process.GetCurrentProcess().Id; // Console.WriteLine("curHandel:" + handel); for (uint j = 0; j < count; j++) { // Console.WriteLine("j:"+j); var i = j; SendMessage(hTrayWnd, TB_GETBUTTON, i, address); //取得TBBUTTON結構到本地 int iTmp = 0; var isTrue = ReadProcessMemory(hProcess, address, out btnData, Marshal.SizeOf(btnData), out iTmp); if (isTrue == false) continue; //這一步至關重要,不能省略 //主要解決64位系統電腦運行的是x86的程序 if (btnData.dwData == IntPtr.Zero) { btnData.dwData = btnData.iString; } ReadProcessMemory(hProcess, //從目標進程address處存放的是TBBUTTON btnData.dwData, //取dwData字段指向的TRAYDATA結構 out trayData, Marshal.SizeOf(trayData), out iTmp); UInt32 dwProcessId = 0; GetWindowThreadProcessId(trayData.hwnd, //通過TRAYDATA里的hwnd字段取得本圖標的進程id out dwProcessId); //獲取當前進程id // StringBuilder sb = new StringBuilder(256); // GetModuleFileNameEx(OpenProcess(ProcessAccess.AllAccess, false, dwProcessId), IntPtr.Zero, sb, 256); // Console.WriteLine(sb.ToString()); if (dwProcessId == (UInt32) handel) { Rect rect = new Rect(); IntPtr lngRect = VirtualAllocEx(hProcess, //在目標進程中申請一塊內存,放TBBUTTON信息 IntPtr.Zero, Marshal.SizeOf(typeof (Rect)), AllocationType.Commit, MemoryProtection.ReadWrite); i = j; SendMessage(hTrayWnd, TB_GETITEMRECT, i, lngRect); isTrue = ReadProcessMemory(hProcess, lngRect, out rect, Marshal.SizeOf(rect), out iTmp); //釋放內存 VirtualFreeEx(hProcess, lngRect, Marshal.SizeOf(rect), FreeType.Decommit); VirtualFreeEx(hProcess, lngRect, 0, FreeType.Release); int left = rectTray.Left + rect.Left; int top = rectTray.Top + rect.Top; int botton = rectTray.Top + rect.Bottom; int right = rectTray.Left + rect.Right; rectNotify = new Rect(); rectNotify.Left = left; rectNotify.Right = right; rectNotify.Top = top; rectNotify.Bottom = botton; isFind = true; break; } } VirtualFreeEx(hProcess, address, 0x4096, FreeType.Decommit); VirtualFreeEx(hProcess, address, 0, FreeType.Release); CloseHandle(hProcess); } return isFind; }
7.如果沒有找到,那么需要用相同的方法在托盤溢出區域內查找

private IntPtr FindTrayToolbarOverFlowWindow() { IntPtr hWnd = FindWindow("NotifyIconOverflowWindow", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null); } return hWnd; }
在查找中的難點:
1.對於32位操作系統和64位操作系統,系統內部處理方式不一樣,所以許多時候當去取TBBUTTON結構到本地的時候得到的地址為0,這里查詢了一些資料,網上一些資料TBBUTTON的結構體如下:

[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TBBUTTON { public int iBitmap; public int idCommand; public byte fsState; public byte fsStyle; public byte bReserved0; public byte bReserved1; public IntPtr dwData; public IntPtr iString; }
這個在32位下面沒有問題,但是在64位系統下就出現了問題,后面參考網上一些資料,原來問題出在中間4個byte中,由於32位系統中4個byte剛好32位,但是在64位中這里就不對,所以就修改為如下:

[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TBBUTTON { public int iBitmap; public int idCommand; public IntPtr fsStateStylePadding; public IntPtr dwData; public IntPtr iString; }
修改過后在64位系統中運行通過了,在這樣一位問題解決了,但是當我將解決方案遷移到程序當中的時候,卻出了問題,一直不能得到地址,查找了很多原因,原來是在我程序編譯的時候,生成的平台是X86,那么就造成了64位系統中使用32位平台時出現問題:
到這里我就猜想是不是 public IntPtr fsStateStylePadding;這一句出了問題,當時x86平台的時候,這個只占用了32位,但是實際64位系統這個位置應該要占用64位,造成地址不對,出錯了。
那么接下來我就證實了下這個問題,在我獲取地址的時候在public IntPtr dwData;字段中沒有獲取到,但是在public IntPtr iString;字段中獲取到了,那么證明我的猜想是對的(真正是否正確還需要指正),
那么解決方案就來了,為了更好的兼容性,結構體不變,當獲取dwData地址沒有獲取到的時候,我們查找iString字段就好了,方法在這里:

//這一步至關重要,不能省略 //主要解決64位系統電腦運行的是x86的程序 if (btnData.dwData == IntPtr.Zero) { btnData.dwData = btnData.iString; }
把這個主要的解決了,后面就是查找當前托盤圖標相對於父窗體的位置了,使用了很多方法:
GetWindowRect
ScreenToClient
GetClientRect
這些方法都沒有成功,最后發現網上有這么一種方法。

SendMessage(hTrayWnd, TB_GETITEMRECT, i, lngRect); isTrue = ReadProcessMemory(hProcess, lngRect, out rect, Marshal.SizeOf(rect), out iTmp);
在這里真正感受到c++的強大。
在解決這個問題的過程中,參考了很多方案,通過整合才解決了這個問題,如下:
http://blog.163.com/zjlovety@126/blog/static/22418624201142763542917/
http://blog.csdn.net/wzsy/article/details/47980317
http://www.cnblogs.com/hanf/archive/2011/08/09/2131641.html
等。
以下是調用方法:

private void Load() { this._notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick; _notifyIcon.MouseMove += new MouseEventHandler(notifyIcon_MouseMove); CreateNotifyMouseHelper(); } private NotifyIconMouseHelper notifyHelper; private Timer timer = null; private void CreateNotifyMouseHelper() { notifyHelper=new NotifyIconMouseHelper(); notifyHelper.MouseEnterNotifyStatusChanged+= MouseEnterNotifyStatusChanged; } private void MouseEnterNotifyStatusChanged(object sender, bool isEnter) { if (isEnter) { Console.WriteLine("鼠標進入"); } else { Console.WriteLine("鼠標離開"); } }
以下是檢測的源代碼:

using System; using System.Collections.Generic; using System.Data.Entity.Core.Metadata.Edm; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Timers; using System.Windows; namespace NotifyTest { /*托盤圖標鼠標進入離開事件 */ public delegate void MouseEnterNotifyStatusChangedHandel(object sender, bool isEnter); public class NotifyIconMouseHelper { #region win32類庫 [Flags()] public enum ProcessAccess : int { /// <summary>Specifies all possible access flags for the process object.</summary> AllAccess = CreateThread | DuplicateHandle | QueryInformation | SetInformation | Terminate | VMOperation | VMRead | VMWrite | Synchronize, /// <summary>Enables usage of the process handle in the CreateRemoteThread function to create a thread in the process.</summary> CreateThread = 0x2, /// <summary>Enables usage of the process handle as either the source or target process in the DuplicateHandle function to duplicate a handle.</summary> DuplicateHandle = 0x40, /// <summary>Enables usage of the process handle in the GetExitCodeProcess and GetPriorityClass functions to read information from the process object.</summary> QueryInformation = 0x400, /// <summary>Enables usage of the process handle in the SetPriorityClass function to set the priority class of the process.</summary> SetInformation = 0x200, /// <summary>Enables usage of the process handle in the TerminateProcess function to terminate the process.</summary> Terminate = 0x1, /// <summary>Enables usage of the process handle in the VirtualProtectEx and WriteProcessMemory functions to modify the virtual memory of the process.</summary> VMOperation = 0x8, /// <summary>Enables usage of the process handle in the ReadProcessMemory function to' read from the virtual memory of the process.</summary> VMRead = 0x10, /// <summary>Enables usage of the process handle in the WriteProcessMemory function to write to the virtual memory of the process.</summary> VMWrite = 0x20, /// <summary>Enables usage of the process handle in any of the wait functions to wait for the process to terminate.</summary> Synchronize = 0x100000 } [StructLayout(LayoutKind.Sequential)] private struct TRAYDATA { public IntPtr hwnd; public UInt32 uID; public UInt32 uCallbackMessage; public UInt32 bReserved0; public UInt32 bReserved1; public IntPtr hIcon; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TBBUTTON { public int iBitmap; public int idCommand; public IntPtr fsStateStylePadding; public IntPtr dwData; public IntPtr iString; } [Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000, Decommit = 0x4000, Release = 0x8000, Reset = 0x80000, Physical = 0x400000, TopDown = 0x100000, WriteWatch = 0x200000, LargePages = 0x20000000 } [Flags] public enum MemoryProtection { Execute = 0x10, ExecuteRead = 0x20, ExecuteReadWrite = 0x40, ExecuteWriteCopy = 0x80, NoAccess = 0x01, ReadOnly = 0x02, ReadWrite = 0x04, WriteCopy = 0x08, GuardModifierflag = 0x100, NoCacheModifierflag = 0x200, WriteCombineModifierflag = 0x400 } [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); [DllImport("user32.dll", SetLastError = true)] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("kernel32.dll")] private static extern IntPtr OpenProcess(ProcessAccess dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern UInt32 SendMessage(IntPtr hWnd, UInt32 Msg, UInt32 wParam, IntPtr lParam); [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType flAllocationType, MemoryProtection flProtect); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, out TBBUTTON lpBuffer, int dwSize, out int lpNumberOfBytesRead ); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, out Rect lpBuffer, int dwSize, out int lpNumberOfBytesRead ); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, out TRAYDATA lpBuffer, int dwSize, out int lpNumberOfBytesRead ); [DllImport("psapi.dll")] private static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] int nSize); [Flags] public enum FreeType { Decommit = 0x4000, Release = 0x8000, } [DllImport("kernel32.dll")] private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, FreeType dwFreeType); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; public POINT(int x, int y) { this.X = x; this.Y = y; } public override string ToString() { return ("X:" + X + ", Y:" + Y); } } [DllImport("user32")] public static extern bool GetClientRect( IntPtr hwnd, out Rect lpRect ); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern bool GetCursorPos(out POINT pt); [StructLayout(LayoutKind.Sequential)] public struct Rect { public int Left; public int Top; public int Right; public int Bottom; } [DllImport("user32.dll")] private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect); public const int WM_USER = 0x0400; public const int TB_BUTTONCOUNT = WM_USER + 24; public const int TB_GETBUTTON = WM_USER + 23; public const int TB_GETBUTTONINFOW = WM_USER + 63; public const int TB_GETITEMRECT = WM_USER + 29; #endregion #region 檢測托盤圖標相對於屏幕位置 private bool FindNotifyIcon(ref Rect rect) { Rect rectNotify = new Rect(); IntPtr hTrayWnd = FindTrayToolbarWindow(); //找到托盤窗口句柄 var isTrue = FindNotifyIcon(hTrayWnd, ref rectNotify); if (isTrue == false) { hTrayWnd = FindTrayToolbarOverFlowWindow(); //找到托盤窗口句柄 isTrue = FindNotifyIcon(hTrayWnd, ref rectNotify); } rect = rectNotify; return isTrue; } private IntPtr FindTrayToolbarWindow() { IntPtr hWnd = FindWindow("Shell_TrayWnd", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "TrayNotifyWnd", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "SysPager", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null); } } } return hWnd; } private IntPtr FindTrayToolbarOverFlowWindow() { IntPtr hWnd = FindWindow("NotifyIconOverflowWindow", null); if (hWnd != IntPtr.Zero) { hWnd = FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null); } return hWnd; } private bool FindNotifyIcon(IntPtr hTrayWnd, ref Rect rectNotify) { UInt32 trayPid = 0; Rect rectTray = new Rect(); GetWindowRect(hTrayWnd, out rectTray); int count = (int) SendMessage(hTrayWnd, TB_BUTTONCOUNT, 0, IntPtr.Zero); //給托盤窗口發消息,得到托盤里圖標 bool isFind = false; if (count > 0) { GetWindowThreadProcessId(hTrayWnd, out trayPid); //取得托盤窗口對應的進程id //獲取托盤圖標的位置 IntPtr hProcess = OpenProcess(ProcessAccess.VMOperation | ProcessAccess.VMRead | ProcessAccess.VMWrite, false, trayPid); //打開進程,取得進程句柄 IntPtr address = VirtualAllocEx(hProcess, //在目標進程中申請一塊內存,放TBBUTTON信息 IntPtr.Zero, 1024, AllocationType.Commit, MemoryProtection.ReadWrite); TBBUTTON btnData = new TBBUTTON(); TRAYDATA trayData = new TRAYDATA(); // Console.WriteLine("Count:"+count); var handel = Process.GetCurrentProcess().Id; // Console.WriteLine("curHandel:" + handel); for (uint j = 0; j < count; j++) { // Console.WriteLine("j:"+j); var i = j; SendMessage(hTrayWnd, TB_GETBUTTON, i, address); //取得TBBUTTON結構到本地 int iTmp = 0; var isTrue = ReadProcessMemory(hProcess, address, out btnData, Marshal.SizeOf(btnData), out iTmp); if (isTrue == false) continue; //這一步至關重要,不能省略 //主要解決64位系統電腦運行的是x86的程序 if (btnData.dwData == IntPtr.Zero) { btnData.dwData = btnData.iString; } ReadProcessMemory(hProcess, //從目標進程address處存放的是TBBUTTON btnData.dwData, //取dwData字段指向的TRAYDATA結構 out trayData, Marshal.SizeOf(trayData), out iTmp); UInt32 dwProcessId = 0; GetWindowThreadProcessId(trayData.hwnd, //通過TRAYDATA里的hwnd字段取得本圖標的進程id out dwProcessId); //獲取當前進程id // StringBuilder sb = new StringBuilder(256); // GetModuleFileNameEx(OpenProcess(ProcessAccess.AllAccess, false, dwProcessId), IntPtr.Zero, sb, 256); // Console.WriteLine(sb.ToString()); if (dwProcessId == (UInt32) handel) { Rect rect = new Rect(); IntPtr lngRect = VirtualAllocEx(hProcess, //在目標進程中申請一塊內存,放TBBUTTON信息 IntPtr.Zero, Marshal.SizeOf(typeof (Rect)), AllocationType.Commit, MemoryProtection.ReadWrite); i = j; SendMessage(hTrayWnd, TB_GETITEMRECT, i, lngRect); isTrue = ReadProcessMemory(hProcess, lngRect, out rect, Marshal.SizeOf(rect), out iTmp); //釋放內存 VirtualFreeEx(hProcess, lngRect, Marshal.SizeOf(rect), FreeType.Decommit); VirtualFreeEx(hProcess, lngRect, 0, FreeType.Release); int left = rectTray.Left + rect.Left; int top = rectTray.Top + rect.Top; int botton = rectTray.Top + rect.Bottom; int right = rectTray.Left + rect.Right; rectNotify = new Rect(); rectNotify.Left = left; rectNotify.Right = right; rectNotify.Top = top; rectNotify.Bottom = botton; isFind = true; break; } } VirtualFreeEx(hProcess, address, 0x4096, FreeType.Decommit); VirtualFreeEx(hProcess, address, 0, FreeType.Release); CloseHandle(hProcess); } return isFind; } #endregion public MouseEnterNotifyStatusChangedHandel MouseEnterNotifyStatusChanged; private object moveObject = new object(); private bool isOver = false; private Timer timer = null; public void MouseEnter() { lock (moveObject) { if (isOver) return; //加載鼠標進入事件 MouseEnter(true); CreateCheckTimer(); timer.Enabled = true; } } private void CreateCheckTimer() { if (timer != null) return; timer = new Timer(); timer.Interval = 120; timer.Elapsed += TimerOnElapsed; } private void TimerOnElapsed(object sender, ElapsedEventArgs arg) { //300毫秒檢測一次 //判斷鼠標是否在托盤圖標內 //如果在,那么就不管 //如果不在,就加載鼠標離開事件,同時停止timer var isEnter = CheckMouseIsEnter(); if (isEnter) return; timer.Enabled = false; MouseEnter(false); } private void MouseEnter(bool isEnter) { isOver = isEnter; if (MouseEnterNotifyStatusChanged == null) return; MouseEnterNotifyStatusChanged(this, isEnter); } public Point Point { get; set; } private bool CheckMouseIsEnter() { //這里怎么檢測呢 //我很無語啊 //第一步:獲取當前鼠標的坐標 //第二步:獲取托盤圖標的坐標 // ???? 難難難難難難難難難難 try { Rect rectNotify = new Rect(); var isTrue = FindNotifyIcon(ref rectNotify); if (isTrue == false) return false; POINT point = new POINT(); GetCursorPos(out point); // Console.WriteLine(string.Format(@" //Left={0} Top={1} Right={2} Bottom={3}", rectNotify.Left, rectNotify.Top, rectNotify.Right, rectNotify.Bottom)); // Console.WriteLine(point.X + " " + point.Y); //第三步:比較鼠標圖標是否在托盤圖標的范圍內 if (point.X >= rectNotify.Left && point.X <= rectNotify.Right && point.Y >= rectNotify.Top && point.Y <= rectNotify.Bottom) { Point = new Point(point.X, point.Y); return true; } else { return false; } } catch (Exception) { return false; } } } }