QQ有個靠邊隐藏的功能,使用起來很友善:在螢幕上拖動QQ的主窗體,當窗體的上邊沿與螢幕的上邊沿對齊時,主窗體就會duang~~地隐藏起來,當将滑鼠移到螢幕上邊沿的對應區域時,主窗體又會duang~~顯示出來。那麼,靠邊隐藏功能到底是怎麼實作的了?
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到我郵箱:[email protected]。
如果你覺得還不錯,請粉我,順便再頂一下啊