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 = ; // 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 = ; // 鼠标钩子句柄 /// <summary>
/// 构造函数
/// </summary>
public MouseHook()
{ } /// <summary>
/// 析构函数
/// </summary>
~MouseHook()
{
Stop();
} /// <summary>
/// 启动全局钩子
/// </summary>
public void Start()
{
// 安装鼠标钩子
if (_hMouseHook == )
{
// 生成一个HookProc的实例.
_mouseHookProcedure = new HookProc(MouseHookProc);
ProcessModule cModule = Process.GetCurrentProcess().MainModule; var mh = GetModuleHandle(cModule.ModuleName);
_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _mouseHookProcedure,mh,); //如果装置失败停止钩子
if (_hMouseHook == )
{
Stop();
throw new Exception("SetWindowsHookEx failed.");
}
}
} /// <summary>
/// 停止全局钩子
/// </summary>
public void Stop()
{
bool retMouse = true; if (_hMouseHook != )
{
retMouse = UnhookWindowsHookEx(_hMouseHook);
_hMouseHook = ;
} // 如果卸下钩子失败
// if (!(retMouse))
// throw new Exception("UnhookWindowsHookEx failed.");
}
int isUp = ;
/// <summary>
/// 鼠标钩子回调函数
/// </summary>
private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
try
{
// 如果正常运行并且用户要监听鼠标的消息
if ((nCode >= ) && (OnMouseActivity != null))
{
MouseButtons button = MouseButtons.None;
int clickCount = ;
switch (wParam)
{
case WM_LBUTTONDOWN:
button = MouseButtons.Left;
clickCount = ;
isUp = ;
break;
case WM_LBUTTONUP:
button = MouseButtons.Left;
clickCount = ;
isUp = ;
break;
case WM_LBUTTONDBLCLK:
button = MouseButtons.Left;
clickCount = ;
break;
case WM_RBUTTONDOWN:
button = MouseButtons.Right;
clickCount = ;
isUp = ;
break;
case WM_RBUTTONUP:
button = MouseButtons.Right;
clickCount = ;
isUp = ;
break;
case WM_RBUTTONDBLCLK:
button = MouseButtons.Right;
clickCount = ;
break;
default:
if (isUp == ) isUp = ;
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 ==  && _isNoticefyShow && _isCanSet == false)
{
//鼠标双击后,移动,并且没有设置
_isCanSet = true;
return;
}

  (3).在鼠标移除托盘图标后,点击鼠标后,接触托盘双击状态

                if ((e.Delta ==  || e.Delta == ) && _isNoticefyShow && _isCanSet)
{
_isNoticefyShow = false;
CheckIsHide(e,rect);
return;
}

具体的实现demo地址如下:

https://gitee.com/sczmzx/WindowAutoHide

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

  1. WPF仿QQ聊天框表情文字混排实现

    原文:WPF仿QQ聊天框表情文字混排实现 二话不说.先上图 图中分别有文件.文本+表情.纯文本的展示,对于同一个list不同的展示形式,很明显,应该用多个DataTemplate,那么也就需要Data ...

  2. wpf 仿QQ图片查看器

    参考博客 WPF下的仿QQ图片查看器 wpf图片查看器,支持鼠标滚动缩放拖拽 实现效果 主要参考的WPF下的仿QQ图片查看器,原博主只给出了部分代码. 没有完成的部分 1.右下角缩略图是原图不是缩略图 ...

  3. C# WPF 仿QQ靠近屏幕上方自动缩起功能实现

    碰到了类似需求,但是上网查了一圈发现感觉要么很复杂,要么代码很臃肿,索性自己实现了一个 几乎和QQ是一模一样的效果,而且核心代码只有20行左右. 代码如下: using System; using S ...

  4. (初学)wpf仿QQ界面-整体布局

    跟一个小学弟一起学习wpf,小学弟是刚初中毕业,对编程刚刚接触,我挺怕自己带的不好,影响小学弟以后在编程方向的学习兴趣.我承认自己水平不高,但是在努力去学习新知识!一起加油吧!在此以博客,记录学习进度 ...

  5. wpf 仿QQ音乐歌词卡拉OK

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

  6. wpf仿QQ之窗体翻转

    很久以前在网上找过窗体翻转的Demo,但看得不是很明白,大多是内容的翻转,研究了下,现在分享给大家. 利用UIElement.RenderTransform 属性就能实现元素的呈现位置的转换,因此只需 ...

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

    重要的话 开头说,not for the RecyclerView or ListView, for the Any ViewGroup. 本控件不依赖任何父布局,不是针对 RecyclerView. ...

  8. WPF ”真正的“高仿QQ

    时常可以在各种论坛 博客 看到 各种所谓的 高仿QQ. 说实话 越看越想笑呢.(PS:纯粹的 抨击 那些 不追求 UI 完美主义者) 例如:       本次模仿 采用 C# WPF XAML , 总 ...

  9. WPF开发实例——仿QQ登录界面

    原文:WPF开发实例--仿QQ登录界面 版权声明:本文为博主原创文章,如需转载请标明转载地址 http://blog.csdn.net/u013981858 https://blog.csdn.net ...

随机推荐

  1. EF 连接oracle 基础连接失败 问题解决

    以此记录今天犯下的错误:

  2. 条款49:了解new-handle行为

    多线程下的内存管理与单线程下是完全不同的,因为heap是一个可以被全局改动的资源,所以所有的线程都有可能去访问这一资源,这回导致很多的race_conditions.   当operator new未 ...

  3. Hibernate(1)

    一.什么是hibernate 1. hibernate是开源的轻量级框架,应用在javaee三层结构中 dao层框架,使用orm思想对数据库进行crud操作 2 .在dao层里面做对数据库crud操作 ...

  4. PHP用mysql_insert_id()函数获得刚插入数据或当前发布文章的ID

    向mysql 插入数据时,很多时候我们想知道刚刚插入数据的id,这对我们很有用.下面这篇文章就详细给大家介绍了利用mysql_insert_id()函数获得刚插入数据或当前发布文章的ID,有需要的朋友 ...

  5. git,npm,bower设置代理地址

    我們先假設代理伺服器的位址為: http://10.0.0.1:8080 設定 Git 使用代理伺服器 輸入兩行指令即可設定完畢: git config --global https.proxy ht ...

  6. 【SQL查询】查询的值为空时,给出默认值_NVL函数

    格式为: NVL( string1, replace_with) 功能:如果string1为NULL,则NVL函数返回replace_with的值,否则返回string1的值. 引申一下,此NVL的作 ...

  7. UVALive 3635 Pie(二分法)

    简单的二分法应用,循环1000次精度就满足要求了. #include<iostream> #include<cstdio> #include<cstdlib> #i ...

  8. Codeforces Round #246 (Div. 2) C. Prime Swaps(贪心,数论)

    题目链接:http://codeforces.com/contest/432/problem/C 首先由题意分析出:这些数是从1到n且各不相同,所以最后结果肯定是第i位的数就是i. 采用这样一种贪心策 ...

  9. Rebranding(字母代换)

    个人心得:题目意思就是每次给出可以互换的字母,如果每次命令的时候就执行的话一定会超时. 所以我就是将输入的字母从a到z的数目和路径依次保存,再建立一个book数组表示字母现在所指的字母 ,一开始就直接 ...

  10. 「长乐集训 2017 Day1」区间 线段树

    题目 对于两个区间\((a,b),(c,d)\),若\(c < a < d\)或\(c < b < d\)则可以从\((a,b)\)走到\((c,d)\)去,现在有以下两种操作 ...