wpf仿qq边缘自动停靠,支持多屏
wpf完全模仿qq边缘自动隐藏功能,采用鼠标钩子获取鼠标当前状态,在通过当前鼠标的位置和点击状态来计算是否需要隐藏。
以下是实现的具体方法:
一、鼠标钩子实时获取当前鼠标的位置和点击状态
/// <summary> /// 鼠标全局钩子 /// </summary> public class MouseHook { private const int WM_MOUSEMOVE = 0x200; private const int WM_LBUTTONDOWN = 0x201; private const int WM_RBUTTONDOWN = 0x204; private const int WM_MBUTTONDOWN = 0x207; private const int WM_LBUTTONUP = 0x202; private const int WM_RBUTTONUP = 0x205; private const int WM_MBUTTONUP = 0x208; private const int WM_LBUTTONDBLCLK = 0x203; private const int WM_RBUTTONDBLCLK = 0x206; private const int WM_MBUTTONDBLCLK = 0x209; /// <summary> /// 点 /// </summary> [StructLayout(LayoutKind.Sequential)] public class POINT { public int x; public int y; } /// <summary> /// 钩子结构体 /// </summary> [StructLayout(LayoutKind.Sequential)] public class MouseHookStruct { public POINT pt; public int hWnd; public int wHitTestCode; public int dwExtraInfo; } public const int WH_MOUSE_LL = 14; // mouse hook constant // 装置钩子的函数 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); // 卸下钩子的函数 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool UnhookWindowsHookEx(int idHook); // 下一个钩挂的函数 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern IntPtr GetModuleHandle(string lpModuleName); // 全局的鼠标事件 public event MouseEventHandler OnMouseActivity; // 钩子回调函数 public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); // 声明鼠标钩子事件类型 private HookProc _mouseHookProcedure; private static int _hMouseHook = 0; // 鼠标钩子句柄 /// <summary> /// 构造函数 /// </summary> public MouseHook() { } /// <summary> /// 析构函数 /// </summary> ~MouseHook() { Stop(); } /// <summary> /// 启动全局钩子 /// </summary> public void Start() { // 安装鼠标钩子 if (_hMouseHook == 0) { // 生成一个HookProc的实例. _mouseHookProcedure = new HookProc(MouseHookProc); ProcessModule cModule = Process.GetCurrentProcess().MainModule; var mh = GetModuleHandle(cModule.ModuleName); _hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _mouseHookProcedure,mh,0); //如果装置失败停止钩子 if (_hMouseHook == 0) { Stop(); throw new Exception("SetWindowsHookEx failed."); } } } /// <summary> /// 停止全局钩子 /// </summary> public void Stop() { bool retMouse = true; if (_hMouseHook != 0) { retMouse = UnhookWindowsHookEx(_hMouseHook); _hMouseHook = 0; } // 如果卸下钩子失败 // if (!(retMouse)) // throw new Exception("UnhookWindowsHookEx failed."); } int isUp = 0; /// <summary> /// 鼠标钩子回调函数 /// </summary> private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam) { try { // 如果正常运行并且用户要监听鼠标的消息 if ((nCode >= 0) && (OnMouseActivity != null)) { MouseButtons button = MouseButtons.None; int clickCount = 0; switch (wParam) { case WM_LBUTTONDOWN: button = MouseButtons.Left; clickCount = 1; isUp = 1; break; case WM_LBUTTONUP: button = MouseButtons.Left; clickCount = 1; isUp = 2; break; case WM_LBUTTONDBLCLK: button = MouseButtons.Left; clickCount = 2; break; case WM_RBUTTONDOWN: button = MouseButtons.Right; clickCount = 1; isUp = 1; break; case WM_RBUTTONUP: button = MouseButtons.Right; clickCount = 1; isUp = 2; break; case WM_RBUTTONDBLCLK: button = MouseButtons.Right; clickCount = 2; break; default: if (isUp == 2) isUp = 0; break; } // 从回调函数中得到鼠标的信息 MouseHookStruct MyMouseHookStruct = (MouseHookStruct) Marshal.PtrToStructure(lParam, typeof (MouseHookStruct)); var x = MyMouseHookStruct.pt.x; var y = MyMouseHookStruct.pt.y; MouseEventArgs e = new MouseEventArgs(button, clickCount, x, y, isUp); // 如果想要限制鼠标在屏幕中的移动区域可以在此处设置 // 后期需要考虑实际的x、y的容差 if (!Screen.PrimaryScreen.Bounds.Contains(e.X, e.Y)) { //return 1; } OnMouseActivity(this, e); } } catch (Exception ex) { Debug.WriteLine(ex); } // 启动下一次钩子 return CallNextHookEx(_hMouseHook, nCode, wParam, lParam); } }
二、判断窗口是否在屏幕边缘
如果是在屏幕边缘,并且鼠标离开窗体,那么就需要隐藏窗口
/// <summary> /// 检测是否需要隐藏窗体 /// </summary> /// <param name="e"></param> /// <param name="rect"></param> private void CheckIsHide(MouseEventArgs e,System.Drawing.Rectangle rect) { var x = e.X; var y = e.Y; if (x < rect.Left) x = rect.Left; if (x > rect.Right) x = rect.Right; if (y < rect.Top) y = rect.Top; if (y > rect.Bottom) y = rect.Bottom; bool isLeave = !(x >= this.Left && x <= (this.Left + this.ActualWidth) && y >= this.Top && y <= this.Top + this.ActualHeight); if (!isLeave) { //鼠标在窗体内移动时解除双击状态 _isNoticefyShow = false; return; } //isLeave=true if (_isNoticefyShow == false) { //顶部判断 if (this.Top - _border < rect.Top) { SetIsHide(true,rect); //这里修正高度为边界高度,这样做的原因主要是避免鼠标移动到边框上面时出现闪动 _oldTop = rect.Top; UpdateLeft(rect); } //左边判断 else if (this.Left - _border < rect.Left) { SetIsHide(true, rect); //这里修正左边 _oldLeft = rect.Left; this.Left = rect.Left - this.ActualWidth; UpdateTop(rect); } //右边判断 else if (this.Left+this.ActualWidth + _border > rect.Right) { SetIsHide(true, rect); //修正右边 _oldLeft = rect.Right - this.ActualWidth; this.Left = rect.Right; UpdateTop(rect); } } }
三、窗口隐藏时根据鼠标位置判断是否需要显示
如果鼠标在边框位置,并且进入了上次窗体隐藏的边框内,那么就显示窗体
/// <summary> /// 判断鼠标时候在窗体边缘 /// </summary> /// <returns></returns> private bool CheckMouseIsWindowBorder(MouseEventArgs e, System.Drawing.Rectangle rect) { //获取边界的值 //判断top if (e.Y - _border <= rect.Top && e.X >= this.Left && e.X <= (this.Left + this.ActualWidth)) { return true; } //判断left if (e.X - _border<=rect.Left && e.Y >= this.Top && e.Y <= (this.Top + this.ActualHeight)) { //显示 return true; } //判断right if (this.Left > rect.Left + _border && e.X + _border >= rect.Right && e.Y >= this.Top && e.Y <= (this.Top + this.ActualHeight)) { return true; } //SystemInformation.VirtualScreen. //判断右边 return false; }
四、双击托盘图标显示窗体
显示窗体简单,直接展示就可以,关键是要实现窗体显示后如果窗体还在屏幕边框位置,那么需要判断什么情况下需要隐藏窗体,现在有以下两种情况需要隐藏:
1.鼠标在离开任务栏后经过窗口后在离开窗口,窗口需要自动隐藏
2.鼠标在离开任务栏后在窗口外的其它位置点击,触发窗口自动隐藏
1情况容易,在进入窗体时清空托盘图标点击的标记就可以了
2情况处理有点麻烦,由于在窗口外的其他位置点击这个事件在双击托盘图标的时候也会触发,我们要屏蔽掉这时这个触发条件,只有在托盘外面时去点击才有效,具体的方案是,在鼠标从托盘移动时才标记点击事件有效,这样就可以避免顺序错乱了,部分代码如下;
(1).在双击托盘图标时标记状态:
_isNoticefyShow = true; _isCanSet = false;
(2).鼠标双击后,移动
if (e.Delta == 0 && _isNoticefyShow && _isCanSet == false) { //鼠标双击后,移动,并且没有设置 _isCanSet = true; return; }
(3).在鼠标移除托盘图标后,点击鼠标后,接触托盘双击状态
if ((e.Delta == 1 || e.Delta == 2) && _isNoticefyShow && _isCanSet) { _isNoticefyShow = false; CheckIsHide(e,rect); return; }
具体的实现demo地址如下:
https://gitee.com/sczmzx/WindowAutoHide