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. ionic3 教程(一)安装和配置

    // 安装(失败的话 Mac 尝试使用 sudo,Windows 尝试管理员身份运行 cmd) $ npm install -g cordova ionic // 安装后可以验证一下 ionic cl ...

  2. 分布式_理论_06_ 一致性算法 Raft

    一.前言 五.参考资料 1.分布式理论(六)—— Raft 算法 2.分布式理论(六) - 一致性协议Raft

  3. SQL Server数据文件迁移

    需求:源SQL Server安装目录及数据目录 与 目标SQL Server安装目录及数据目录 完全不同. 步骤: 1.拷贝源数据目录下需要移植的库文件(rpBrInfo_TA.mdf.rpBrInf ...

  4. CANopenSocket 测试

    /************************************************************************* * CANopenSocket 测试 * 说明: ...

  5. bzoj 5016 一个简单的询问

    THUWC 考了莫队(这个应该可以说吧) 然而不会莫队,签到失败,所以找到了一道长得差不多的题写一写 为什么这么长时间都没有发现这道题(半恼 给你一个长度为N的序列ai,1≤i≤N和q组询问,每组询问 ...

  6. 19年博客flag

    目录 为什么没有年终总结 为什么今天更新了 19年博客flag 个人博客链接:我在马路边 https://hhongwen.cn 更好的阅读体验点击查看原文:19年博客flag 原创博客,转载请注明出 ...

  7. java 使用最新api操作mongodb

    // package com.auto.test.dbmodel; import java.util.ArrayList; import org.bson.Document;import org.bs ...

  8. 学习动态性能表(14)--v$parameter&v$system_parameter

    学习动态性能表 第14篇--V$PARAMETER&V$SYSTEM_PARAMETER  2007.6.11 这两个视图列出的各参数项名称以及参数值.V$PARAMETER显示执行查询的se ...

  9. inotify 同步脚本

    #!/bin/bash path1=/home/htoa/tomcat/webapps/ROOT/htoa/ ip=192.168.30.13 /usr/bin/inotifywait -mrq -- ...

  10. vim编辑 小笔记

    http://jingyan.baidu.com/article/495ba8410ff14d38b30ede01.html vim编辑器笔记 1.vi 文件名 打开文件 2.按 i 键 进入inse ...