1. 程式人生 > >wpf仿qq邊緣自動停靠,支持多屏

wpf仿qq邊緣自動停靠,支持多屏

QQ switch marshal 消息 ldl 區域 nco 根據 close

  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;
View Code

  (2).鼠標雙擊後,移動

技術分享圖片
                if (e.Delta == 0 && _isNoticefyShow && _isCanSet == false)
                {
                    //鼠標雙擊後,移動,並且沒有設置
                    _isCanSet = true;
                    return;
                }
View Code

  (3).在鼠標移除托盤圖標後,點擊鼠標後,接觸托盤雙擊狀態

技術分享圖片
                if ((e.Delta == 1 || e.Delta == 2) && _isNoticefyShow && _isCanSet)
                {
                    _isNoticefyShow = false;
                    CheckIsHide(e,rect);
                    return;
                }        
View Code

具體的實現demo地址如下:

https://gitee.com/sczmzx/WindowAutoHide

wpf仿qq邊緣自動停靠,支持多屏