QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。

  我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。

那么,靠边隐藏功能到底是怎么实现的了?(最初实现的过程中,遇到了很多问题,花了不少时间,现在直接把成果共享出来)

想要直接下载体验的朋友请点击:“下载中心”

一.靠边隐藏的原理

靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:

方案说明如下:

(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。

(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。

(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:

a. 当鼠标再度离开窗体区域,则又隐藏窗体。

b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。

二.具体实现过程

1.基本元素定义

  首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:

    /// <summary>
/// 靠边隐藏的类型。
/// </summary>
public enum DockHideType
{
/// <summary>
/// 不隐藏
/// </summary>
None = ,
/// <summary>
/// 靠上边沿隐藏
/// </summary>
Top,
/// <summary>
/// 靠左边沿隐藏
/// </summary>
Left,
/// <summary>
/// 靠右边沿隐藏
/// </summary>
Right
}

其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:

    /// <summary>
/// 窗体的显示或隐藏状态
/// </summary>
public enum FormDockHideStatus
{
/// <summary>
/// 已隐藏
/// </summary>
Hide = , /// <summary>
/// 准备隐藏
/// </summary>
ReadyToHide, /// <summary>
/// 正常显示
/// </summary>
ShowNormally }

2.判断是否达到隐藏条件

  很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。

        private void dockedForm_LocationChanged(object sender, EventArgs e)
{
this.ComputeDockHideType();
if (!this.IsOrg)
{
this.lastBoard = this.dockedForm.Bounds;
this.IsOrg = true;
}
} /// <summary>
/// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
/// </summary>
private void ComputeDockHideType()
{
if (this.dockedForm.Top <= )
{
this.dockHideType = DockHideType.Top;
if (this.dockedForm.Bounds.Contains(Cursor.Position))
{
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
return;
}
this.formDockHideStatus = FormDockHideStatus.Hide;
return;
}
else
{
if (this.dockedForm.Left <= )
{
this.dockHideType = DockHideType.Left;
if (this.dockedForm.Bounds.Contains(Cursor.Position))
{
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
return;
}
this.formDockHideStatus = FormDockHideStaus.Hide;
return;
}
else
{
if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
{
this.dockHideType = DockHideType.None;
this.formDockHideStatus = FormDockHideStatus.ShowNormally;
return;
}
this.dockHideType = DockHideType.Right;
if (this.dockedForm.Bounds.Contains(Cursor.Position))
{
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
return;
}
this.formDockHideStatus = FormDockHideStatus.Hide;
return;
}
}
}

  上面的代码主要体现了以下几个要点:

(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。

(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。

(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。

详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。

3.定时检测满足/退出隐藏条件

我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。

        /// <summary>
/// 定时器循环判断。
/// </summary>
private void CheckPosTimer_Tick(object sender, EventArgs e)
{//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
if (this.dockedForm.Bounds.Contains(Cursor.Position))
{ if (this.dockHideType!= DockHideType.Top)
{
if (this.dockHideType!= DockHideType.Left)
{
if (this.dockHideType!= DockHideType.Right)
{
return;
}
if (this.formDockHideStatus == FormDockHideStatus.Hide)
{
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
return;
}
}
else
{
if (this.formDockHideStatus == FormDockHideStatus.Hide)
{
this.dockedForm.Location = new Point(, this.dockedForm.Location.Y);
return;
}
}
}
else
{
if (this.formDockHideStatus == FormDockHideStatus.Hide)
{
this.dockedForm.Location = new Point(this.dockedForm.Location.X, );
return;
}
}
}
else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
{ switch (this.dockHideType)
{
case DockHideType.None:
{
if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&
(this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
{
this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height);
}
break;
}
case DockHideType.Top:
{
this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - ) * -);
return;
}
case DockHideType.Left:
{
this.dockedForm.Location = new Point(- * (this.dockedForm.Width - ), this.dockedForm.Location.Y);
return;
}
default:
{
if (anchorStyles2 != DockHideType.Right)
{
return;
}
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - , this.dockedForm.Location.Y);
return;
}
}
}
}

(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。

(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。

(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。

三.如何使用AutoDocker组件

  AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:

从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:

  this.autoDocker1.Initialize(this);

  这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~

在GG 4.2的源码中,找到客户端项目(GG2014)下的AutoDocker.cs文件,即可详细研究靠边隐藏的实现细节。

四.GG V4.2 源码

  下载最新版本,请转到这里

________________________________________________________________________

欢迎和我探讨关于 GG 和 GGMeeting 的一切,我的QQ:2027224508,多多交流!

大家有什么问题和建议,可以留言,也可以发送email到我邮箱:2027224508@qq.com。

如果你觉得还不错,请粉我,顺便再顶一下啊

QQ揭秘:如何实现窗体靠边隐藏?【低调赠送:QQ高仿版GG 4.2 最新源码】的更多相关文章

  1. 即时通信系统中如何实现:聊天消息加密,让通信更安全? 【低调赠送:QQ高仿版GG 4.5 最新源码】

    加密重要的通信消息,是一个常见的需求.在一些政府部门的即时通信软件中(如税务系统),对聊天消息进行加密是非常重要的一个功能,因为谈话中可能会涉及到机密的数据.我在最新的GG 4.5中,增加了对聊天消息 ...

  2. QQ揭秘:如何实现托盘闪动消息提醒?【低调赠送:QQ高仿版GG 4.1 最新源码】

    当QQ收到好友的消息时,托盘的图标会变成好友的头像,并闪动起来,点击托盘,就会弹出与好友的聊天框,随即,托盘恢复成QQ的图标,不再闪动.当然,如果还有其它的好友的消息没有提取,托盘的图标会变成另一个好 ...

  3. 如何做到在虚拟数据库和真实数据库之间自由切换?【低调赠送:QQ高仿版GG 4.4 最新源码】

    记得以前在公司上班时,有时候白天的活没干完,我就会把工作带回家晚上加班继续做.但是,我们开发用的数据库是部署在公司局网内部的一台服务器上的,在家里是肯定连不上这台机器的.在家里没有数据库,服务端就跑不 ...

  4. 如何实现:录制视频聊天的全过程? 【低调赠送:QQ高仿版GG 4.3 最新源码】

    前段时间做个项目,客户需要将视频对话的整个过程录制下来,这样,以后就可以随时观看.想来录制整个视频聊天的过程这样的功能应该是个比较常见的需求,比如,基于网络语音视频的1:1的英语口语辅导,如果能将辅导 ...

  5. 即时通信系统中如何实现:全局系统通知,并与Web后台集成?【低调赠送:QQ高仿版GGTalk 5.1 最新源码】

    像QQ这样的即时通信软件,时不时就会从桌面的右下角弹出一个小窗口,或是显示一个广告.或是一个新闻.或是一个公告等.在这里,我们将其统称为“全局系统通知”.很多使用GGTalk的朋友都建议我加上一个类似 ...

  6. 可在广域网部署运行的QQ高仿版 -- GG叽叽(源码)

    前段时间看到园子里有朋友开发了QQ高仿版的程序,我也非常有兴趣,以前一直有个做即时聊天程序的梦,趁这段时间工作不是很忙,就开始动手来做这个事情.根据我以往积累下来的项目经验,实现QQ的基本功能,问题应 ...

  7. 即时通信系统中实现全局系统通知,并与Web后台集成【附C#开源即时通讯系统(支持广域网)——QQ高仿版IM最新源码】

    像QQ这样的即时通信软件,时不时就会从桌面的右下角弹出一个小窗口,或是显示一个广告.或是一个新闻.或是一个公告等.在这里,我们将其统称为“全局系统通知”.很多使用C#开源即时通讯系统——GGTalk的 ...

  8. 即时通信系统中实现聊天消息加密,让通信更安全【低调赠送:C#开源即时通讯系统(支持广域网)——GGTalk4.5 最新源码】

    在即时通讯系统(IM)中,加密重要的通信消息,是一个常见的需求.尤其在一些政府部门的即时通信软件中(如税务系统),对即时聊天消息进行加密是非常重要的一个功能,因为谈话中可能会涉及到机密的数据.我在最新 ...

  9. iOS天气动画、高仿QQ菜单、放京东APP、高仿微信、推送消息等源码

    iOS精选源码 TYCyclePagerView iOS上的一个无限循环轮播图组件 iOS高仿微信完整项目源码 想要更简单的推送消息,看本文就对了 ScrollView嵌套ScrolloView解决方 ...

随机推荐

  1. Codeforces 731C. Socks 联通块

    C. Socks time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input o ...

  2. 3. Builder(建造者)

    意图: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 适用性: 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时. 当构造过程必须允许被构造的对象有不同 ...

  3. PHP 图片生成文字

    $dst_path = './1.png'; $font_file = './ADOBEHEITISTD-REGULAR (V5.010).OTF'; $img_bg = imagecreatefro ...

  4. jekyll安装的斗智斗勇

    jekyll---将纯文本转化为静态网站和博客,GitHub Pages 可以运行 Jekyll,你很简单就可以完全免费的在 GitHub 上发布网站. 小白安装jekyll时的若干问题,有错误欢迎指 ...

  5. 站内信对话列表sql语句

  6. 【转】java架构师之路:JAVA程序员必看的15本书的电子版下载地址

    作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从.我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序,推荐给大家,特别是那些想不断提高自己技术水 ...

  7. Mysql 启动不了,问题集锦

    1. 报错信息 mysqld_safe mysqld from pid file xxx.pid ended 解决办法: 可能是pid所在目录,没有权限,赋予权限即可 2. 找不到 /tmp/mysq ...

  8. Android上dip、dp、px、sp等单位说明(转)

    dip  device independent pixels(设备独立像素). 不同设备不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA.HVGA和QVGA 推荐使用这个,不依赖像素. 在 ...

  9. 关于mock server

    这篇技术博客是在知乎上看到的 知乎js大神张云龙写的 这里贴过来记录下,如果侵权 请告知将及时删除. --------------------------- 为了更好的分工合作,让前端能在不依赖后端环 ...

  10. UVALive 2191 Potentiometers (树状数组)

    题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...