QQ揭祕:如何實現窗體靠邊隱藏?【低調贈送:QQ高仿版GG 4.2 最新原始碼】

C#開源即時通訊GGTalk發表於2015-03-25

      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。  

如果你覺得還不錯,請粉我,順便再頂一下啊

 

相關文章