zoukankan      html  css  js  c++  java
  • QQ揭秘:如何实现窗体靠边隐藏?【低调赠送:QQ高仿版GG 4.2 最新源码】

          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 = 0,
            /// <summary>
            /// 靠上边沿隐藏
            /// </summary>
            Top,
            /// <summary>
            /// 靠左边沿隐藏
            /// </summary>
            Left,
            /// <summary>
            /// 靠右边沿隐藏
            /// </summary>
            Right
        }

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

        /// <summary>
        /// 窗体的显示或隐藏状态
        /// </summary>
        public enum FormDockHideStatus
        {
            /// <summary>
            /// 已隐藏
            /// </summary>
            Hide = 0,
    
            /// <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 <= 0)
                {
                    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 <= 0)
                    {
                        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(0, this.dockedForm.Location.Y);
                                return;
                            }
                        }
                    }
                    else
                    {
                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
                        {
                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
                            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 - 4) * -1); return; } case DockHideType.Left: { this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y); return; } default: { if (anchorStyles2 != DockHideType.Right) { return; } this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, 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。  

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

  • 相关阅读:
    HTML+CSS简单实现导航栏二级下拉菜单
    原创 | 我的个人微信公众号
    原创 | 喂,在吗?
    NodeJs实现邮箱验证
    JS排序算法(二)冒泡排序
    JS排序算法(一) 快速排序
    前端常见的布局方式
    JS继承方式
    前端Node实现简易的文件上传下载
    原生js实现深度克隆
  • 原文地址:https://www.cnblogs.com/justnow/p/4362891.html
Copyright © 2011-2022 走看看