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

原文地址:https://www.cnblogs.com/sczmzx/p/8996371.html

时间: 2024-11-07 12:36:53

wpf仿qq边缘自动停靠,支持多屏的相关文章

wpf 仿QQ音乐歌词卡拉OK

最近用WPF做了个音乐播放器,读取歌词.歌词同步都已经实现了.卡拉OK逐字变色 也实现了,但是逐字变色时不能根据歌手唱的快慢来逐字显示.请问各位大神,这个如何解决,有何思路?(附上我做的界面) 感谢各位大神光顾和赐教!!!抱拳........

【Android】史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/53157090 本文出自:[张旭童的博客](http://blog.csdn.net/zxt0601) 代码传送门:喜欢的话,随手点个star.多谢 https://github.com/mcxtzhang/SwipeDelMenuLayout 重要的话 开头说,not for the RecyclerView or L

史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。

重要的话 开头说,not for the RecyclerView or ListView, for the Any ViewGroup. 本控件不依赖任何父布局,不是针对 RecyclerView.ListView,而是任意的ViewGroup里的childView都可以使用侧滑(删除)菜单.支持任意ViewGroup.0耦合.史上最简单. 概述 本控件从撸出来在项目使用至今已经过去7个月,距离第一次将它push至github上,也已经2月+.(之前,我发表过一篇文章.传送门:http://b

WPF下的仿QQ图片查看器

本例中的大图模式使用图片控件展示,监听控件的鼠标滚轮事件和移动事件,缩略图和鹰眼模式采用装饰器对象IndicatorObject和Canvas布局.百分比使用一个定时器,根据图片的放大倍数计算具体的数值显示. 首先看看效果图: 以下开始绘制图片 定义缩略图上白色的矩形,这其实是一个Indicator,它的外围是一个Canvas,然后缩略图是一个Image控件 internal class IndicatorObject : ContentControl { private MaskCanvas

高仿QQ即时聊天软件开发系列之二登录窗口界面

继上一篇高仿QQ即时聊天软件开发系列之一开端之后,开始做登录窗口 废话不多说,先看效果,只有界面 可能还有一些细节地方没有做,例如那个LOGO嘛,不要在意这些细节 GIF虽短,可是这做起来真难,好吧因为我没玩过WPF所以难,因为感觉做出来之后也就那样 整体布局 整体是上下分,下面是左中右分 1 <Grid> 2 <Grid.RowDefinitions> 3 <RowDefinition Height="27"><!--用于放窗口右上角关闭.最

高仿QQ头像截取

花费了半天时间,把 仿QQ头像截取的方法整理了下,并制作了一个demo以供大家参考,基本上实现了qq中我的资料界面上(包括背景透明化,上滑标题栏显示,下拉隐藏等)的大致效果,先上图看效果吧: 支持的功能: 1.选择裁剪的图片支持手势放大缩小(包括双击放大缩小): 2.判断图片边缘,即裁剪区域不会超过图片边缘: 原理: 一个重写的RelativeLayout,里面放了两层View,下层是重写的支持缩放的ImageView用于放置选择裁剪的图片,ImageView上层是一个自定义View,通过onD

高仿QQ即时聊天软件开发系列之三登录窗口用户选择下拉框

上一篇高仿QQ即时聊天软件开发系列之二登录窗口界面写了一个大概的布局和原理 这一篇详细说下拉框的实现原理 先上最终效果图 一开始其实只是想给下拉框加一个placeholder效果,让下拉框在未选择未输入时显示一个提示字符串.由于Background对ComboBox无效,所以直接通过Background来实现是不行了.需要重新写ComboBox的模板,也就是Template,自定义一个模板来实现这个结果.又看了一下QQ的下拉框,这玩意不自定义也难以实现,所以就干脆自定义了. 先上代码,先是Com

iOS_28仿QQ空间登录与退出

最终效果图如下: 注意事项: 输入框的return Key Main.storyboard中为 LoginController 设置一个storyboardID, 以便可以在代码中通过Storyboard对象实例,创建Main.storyboard里面的控制器 仿QQ窗口抖动 dispach_after模拟延时 输入框的return Key的不同处理方式 Login控制器代码 // // LoginController.m // 28_QQ空间 // // Created by beyond o

Android学习之仿QQ側滑功能的实现

如今项目越来越多的应用了滑动删除的功能,Android本来遵循的是长按删除,IOS定制的是滑动删除,不可否认滑动删除确实在客户体验上要好一点,所以看了非常多关于仿QQ滑动删除的样例,还是感觉代码家的Android Swipe Layout要好一点,至于为何好,以下我给大家实验一下大家就知道了 老规矩.贴上效果图.这样大家才干更近距离的了解 这是代码家的效果图,效果非常多,支持listview.gridview,当然recylerview也是支持的. 可是呢,有个问题,代码家的效果非常多.可是我们