QQ有個靠邊隱藏的功能,使用起來很方便:在屏幕上拖動QQ的主窗體,當窗體的上邊沿與屏幕的上邊沿對齊時,主窗體就會duang~~地隱藏起來,當將鼠標移到屏幕上邊沿的對應區域時,主窗體又會duang~~顯示出來。html
我在GG的最新版4.2中也增長了靠邊隱藏的功能,支持靠左邊沿隱藏、靠上邊沿隱藏、靠右邊沿隱藏三種模式,而且,將靠邊隱藏實現爲了一個可複用的組件AutoDocker。ide
那麼,靠邊隱藏功能究竟是怎麼實現的了?(最初實現的過程當中,遇到了不少問題,花了很多時間,如今直接把成果共享出來)函數
想要直接下載體驗的朋友請點擊:「下載中心」工具
靠邊隱藏的本質實際上並非將窗體的Visiable設爲false,而是將整個窗體的位置挪到屏幕區域以外。好比,靠右邊沿隱藏,實際的效果圖以下所示:this
方案說明以下:spa
(1)當拖動窗體在屏幕上移動時,檢測窗體的位置,是否抵達了屏幕的邊界,若是達到了邊界,則準備靠邊隱藏。code
(2)當達到了隱藏條件,而且鼠標的光標已經離開了主窗體,則實現隱藏。orm
(3)窗體隱藏後,當鼠標的光標移動到窗體與屏幕相交的邊界位置時,則正常顯示窗體;以後:htm
a. 當鼠標再度離開窗體區域,則又隱藏窗體。blog
b.若是鼠標拖動窗體改變了其位置,使其再也不知足隱藏的條件,則以後一直正常顯示窗體。
首先,咱們須要定義靠邊隱藏的類型:靠左、靠上、靠右。使用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 }
很明顯,咱們應當在每次窗體的位置發生變化時,作出這樣的判斷,因此,這個判斷應該在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)是否在窗體內,若是在其內,則爲準備隱藏狀態。
詳細分析一下上面的過程,就會發現,當處於準備隱藏狀態時,若是將鼠標移出到窗體外(此次移動並無拖動窗體改變其位置),那麼,窗體會一直處於「準備隱藏」的狀態。因此,此時,必需要有一個機制來觸發它,真正進行隱藏動做。我是用一個定時器來循環判斷的。
我使用一個定時器,每隔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是以組件(Component)的形式實現的,編譯後,會在工具箱中出現一個AutoDocker組件。其使用很是簡單:
從工具箱中將AutoDocker拖放到主窗體MainForm上,而後在主窗體的構造函數中添加一行代碼:
this.autoDocker1.Initialize(this);
這樣,主窗體運行起來後,就擁有了自動靠邊隱藏的功能了,是否是很duang~~~
在GG 4.2的源碼中,找到客戶端項目(GG2014)下的AutoDocker.cs文件,便可詳細研究靠邊隱藏的實現細節。
下載最新版本,請轉到這裏。
________________________________________________________________________
歡迎和我探討關於 GG 和 GGMeeting 的一切,個人QQ:2027224508,多多交流!
你們有什麼問題和建議,能夠留言,也能夠發送email到我郵箱:2027224508@qq.com。
若是你以爲還不錯,請粉我,順便再頂一下啊