在上一篇《[原創(chuàng)]Silverlight的彈出窗口--展示》中,僅僅是將我制作的基于Silverlight的彈出窗 口作了一個功能性的展示,并提供了一個非;A(chǔ)的版本的源碼,
Silverlight的彈出窗口設(shè)計
。確實這個版本非;A(chǔ),其中存在著眾多未經(jīng)優(yōu)化的代碼,同時結(jié)構(gòu)也存在著一定程度的混亂,因此 如果不從整體上對其進(jìn)行梳理,僅僅通過閱讀代碼恐怕是很難了解其整個工作過程的。
此篇的目的就是,從設(shè)計的結(jié)構(gòu)上,對彈出窗口這一功能作一個大致的介紹,將貫穿于整個作品的設(shè) 計思想描繪出來,而這思想也必然是整作項目中最為穩(wěn)定的部分,在將來不會產(chǎn)生太大的改動。
源碼已經(jīng)有了小部分的更新,如果上一次有下載的園友,請重新下載
整體結(jié)構(gòu)
首先應(yīng)當(dāng)從整體上對這個項目的結(jié)構(gòu)有一個鳥瞰的概念,因此附上架構(gòu)圖一份,當(dāng)然這并不是正規(guī)的 UML圖,但應(yīng)當(dāng)足以說明不少問題
可以看到,在這個結(jié)構(gòu)中,存在著6個非常重要的組件,他們分別是:
PopupService:核心組件,用于提供彈出窗口的功能,所有彈出窗口都由其進(jìn)行控制
LayoutMask:位于彈出窗口下部的遮罩層,同時也將提供模態(tài)對話框打開時屏蔽下層控件的功能
PopupBox:彈出窗口的基類,與LayoutMask之間具有一定的通信和交互能力,同時提供特效等功能
BoxPage/MessagePage:PopupBox的實現(xiàn),分別對應(yīng)不同的功能,但基于此設(shè)計,并不存在太多與實現(xiàn) 相關(guān)的代碼,因此不會作為重點進(jìn)行講述
DragService:管理窗口的鼠標(biāo)拖動功能,從外部引入拖動功能有利于展現(xiàn)與功能的解耦,同時所有與 鼠標(biāo)拖動相關(guān)的實現(xiàn)集中于一個類中,方便了BUG出現(xiàn)之后的定位與修改
PopupService
在我的設(shè)計中,我們不能通過直接的新建對象,如BoxPage box = new BoxPage();來生成一個彈出窗 口。
當(dāng)然最初的設(shè)計確實是可以使用new來創(chuàng)建的,但在日后的測試中發(fā)現(xiàn),這樣的創(chuàng)建會產(chǎn)生很多問題, 在下面例舉一二:
1. 每一個彈出窗口會擁有一個遮罩層,因此后開的彈出窗口一定比之前的在更上層,這就導(dǎo)致先打開 的彈出窗口永遠(yuǎn)不可能被移到最上方,而在日常操作中,點擊后面的窗口應(yīng)當(dāng)可以將這個窗口移到最前方
2. 過多的遮罩層導(dǎo)致了資源大量的占用,當(dāng)打開5個以上彈出窗口時,應(yīng)用程序的響應(yīng)將出現(xiàn)明顯變 慢的現(xiàn)象
這種種缺陷,都將矛頭指向了一處,即我們需要一個統(tǒng)一的環(huán)境來管理彈出窗口及其遮罩層,保證多 個彈出窗口所擁有的遮罩層的“單例性”,因此就產(chǎn)生了通過PopupService這樣一個“服務(wù)”來管理的策 略,其管理方案如下:
1. 一個PopupService對應(yīng)一個Control,此Control就是彈出窗口的擁有者
2. PopupService會產(chǎn)生一個遮罩層與此Control聯(lián)系,并且在此后有彈出窗口時都會使用這唯一的一 個遮罩層
3. 無論何時,對于同一個Control,將只會有一個PopupService進(jìn)行服務(wù)
看到上面的方案后,我想大多數(shù)人都會發(fā)現(xiàn),PopupService變成了“偽單例”或“局部單例”的類, 因此就著“單例模式”的設(shè)計,我們肯定不能簡單地通過new的構(gòu)造來使用這個類,因此就引出了該類的 一個靜態(tài)方法GetServiceFor,此方法接收一個參數(shù)即PopupService的擁有者,當(dāng)然為了能夠?qū)⒄谡謱愉?在控件上,我們要求控件是一個布局控件,即Panel類型。
為了保證PopupService的偽單例的特性,必須將生成的對象保存起來,這里簡單地用到了Dictionary 進(jìn)行保存,當(dāng)調(diào)用GetServiceFor方法時,首先在緩存池中尋找,如果找不到則調(diào)用new進(jìn)行實例化,隨后 放入緩存池中進(jìn)行持久的保存,以保證日后不會再發(fā)生重新構(gòu)造的問題,其具體代碼如下:
public static PopupService GetServiceFor(Panel owner)
{
if (! Cache.ContainsKey(owner))
{
PopupService service = new PopupService (owner);
Cache[owner] = service;
}
return Cache[owner];
}
在這里并沒有顯式地對Cache對象(Dictionary
上面講了PopupService的產(chǎn)生過程,下面例舉一下PopupService提供的方法,由于方法十分簡單,就 不展開討論,其方法主要有3個:
1. GetBoxPage:獲取在此PopupService管理下的BoxPage的一個對象,有重載
2. GetMessagePage:獲取在此PopupService管理下的MessagePage的一個對象,有重載
3. RegisterPopupBox:對已經(jīng)創(chuàng)建好的但沒有納入PopupService管理的PopupBox對象進(jìn)行注冊,注冊 后的對象將由此PopupService進(jìn)行管理,即分配一個LayoutMask
LayoutMask
LayoutMask聽起來就叫“遮罩層”,但其實他不是一個控件,其地位類似于PopupService,是一個“ 管理者”的角色,他將管理多個PopupBox,從而將彈出窗口于PopupService分享開來,起到解耦的作用, 盡可能地減少PopupService的負(fù)擔(dān),從而使程序結(jié)構(gòu)更加清晰。
而從界面的展現(xiàn)角度來講,又可以認(rèn)為LayoutMask確實是一個“控件”,因為他會生成一個Canvas平 鋪于其所有者(Panel類型)之上,此Canvas就是真正的“遮罩層”,對Canvas設(shè)定背景色就會產(chǎn)生模態(tài) 對話框的效果,同時所有的彈出窗口都將作為這個Canvas的子元素,通過ZIndex的改變來確定哪一個對話 框處在最前端,從而得以模仿我們?nèi)粘J褂弥?ldquo;點擊在后面的窗口之后窗口會被置于最前端”的效果,
電腦資料
《Silverlight的彈出窗口設(shè)計》(http://www.oriental01.com)。LayoutMask提供了與彈出窗口的管理相關(guān)的若干方法,其主要對外的方法如下:
1. AddBox:將一個彈出窗口添加進(jìn)來,當(dāng)然在調(diào)用窗口的Show方法之前,窗口是不會顯示出來的, AddBox只是將窗口與本體進(jìn)行聯(lián)系。
2. RemoveBox:與AddBox相反,將一個窗口從本容器中除去,此后即使調(diào)用窗口的Show方法,窗口也 不會再顯示出來了,因此被移除的Box在XAML樹中已經(jīng)是一個孤島,與XAML根沒有聯(lián)系的元素是不可能被 渲染的。
3. PositionBox:在上一個版本中稱為CenterBox,在此版本中加入了新的功能,即連續(xù)打開窗口時, 窗口不會疊在一起,而會按一定的偏移量相互錯開,因此方法也被改名為PositionBox,其作用就是找到 一個合適的位置來放置彈出窗口。
除了公開的方法之外,其部分內(nèi)部方法也有著舉足輕重的作用:
1. CheckModal:每當(dāng)AddBox或RemoveBox調(diào)用時,都會重新檢查是否有彈出窗口是模態(tài)的,如果在這 個LayoutMask管理的彈出對話框中有一個或多個是模態(tài)的,則需要將作為遮罩層使用的Canvas改為模態(tài)以 屏蔽下面的其他控件,其方式是簡單地給Canvas加上背景色。
2. MaxZIndex:返回管理的所有彈出對話框的ZIndex中的最大值,這方便了在后層的對話框移到前層 ,只需要設(shè)定其ZIndex為MaxZIndex + 1即可。
3. ReorderZIndex:當(dāng)然ZIndex是有最大值的,一個很熟悉的數(shù)字65535,所以如果不斷地給窗口增加 ZIndex,必將導(dǎo)致ZIndex超出范圍,這當(dāng)然不是我們所希望的結(jié)果。因此就有了一個方法,當(dāng)ZIndex已經(jīng) 過大的時候,將所有控件的ZIndex進(jìn)行重新排列,按照現(xiàn)有的窗口疊放次序,從1開始重新排列ZIndex, 保證ZIndex永遠(yuǎn)不會超過最大值(當(dāng)然你硬要連續(xù)打開65536個窗口我也真沒辦法……)
4. RenderMask:當(dāng)所有彈出窗口關(guān)閉時,遮罩層也應(yīng)該相應(yīng)消失,而當(dāng)遮罩層未打開時,也不能打開 新的窗口,所以這里就有一個流程,在第一個窗口打開時需要先將遮罩層打開,因此有了RenderMask方法 ,負(fù)責(zé)將遮罩層加入到所有者的子元素中并顯示出來。
DragService:
DragService是一個輔助類,他將提供窗口的鼠標(biāo)拖動功能,這個類的結(jié)構(gòu)也非常簡單,在構(gòu)造的時候 將需要窗口的對象傳遞給他,隨后便可通過設(shè)定IsDraggable屬性來打開/關(guān)閉鼠標(biāo)拖動的功能,對于鼠標(biāo) 拖動的實現(xiàn),在網(wǎng)上無論是FLEX,JS,WINFORM還是Silverlight都有很多的實現(xiàn)了,我使用的也與這些大 同小異,主要就是通過監(jiān)聽MouseDown,MouseUp,MouseMove這3個事件來實現(xiàn),可以看一下IsDraggable 的實現(xiàn):
public bool IsDraggable
{
get
{
return m_IsDraggable;
}
set
{
if (m_IsDraggable == value)
{
return;
}
if (value)
{
EnableDrag();
}
else
{
DisableDrag ();
}
}
}
當(dāng)IsDraggable被設(shè)定為true時,會調(diào)用EnableDrag方法,此方法如下:
private void EnableDrag()
{
PopupBox.DragMouseCaptureArea.MouseLeftButtonDown +=
new MouseButtonEventHandler(Drag_MouseLeftButtonDown);
PopupBox.DragMouseCaptureArea.MouseMove +=
new MouseEventHandler (Drag_MouseMove);
PopupBox.DragMouseCaptureArea.MouseLeftButtonUp +=
new MouseButtonEventHandler(Drag_MouseLeftButtonUp);
}
此方法監(jiān)聽了3個事件,對于拖動的邏輯,是在MouseDown的時候設(shè)定“開始拖動”,在MouseMove的時 候,如果已經(jīng)“開始拖動”,則計算出鼠標(biāo)移動的距離,使窗口移動同樣的距離,最后在MouseUp的時候 “停止拖動”,具體代碼可以在源文件中找到,不再浪費此處的空間了~~
而當(dāng)IsDraggable設(shè)為false的時候,則調(diào)用DisableDrag方法,方法如下:
private void DisableDrag()
{
PopupBox.DragMouseCaptureArea.MouseLeftButtonDown -= Drag_MouseLeftButtonDown;
PopupBox.DragMouseCaptureArea.MouseMove -= Drag_MouseMove;
PopupBox.DragMouseCaptureArea.MouseLeftButtonUp -= Drag_MouseLeftButtonUp;
}
通過注銷3個事件,自然也就停止了拖動的功能。
以后
下一篇將會比較詳細(xì)地去講述一下LayoutMask中的一些方法的實現(xiàn),同時講述諸如動畫效果等復(fù)雜內(nèi) 容的實現(xiàn),最后介紹如何擴(kuò)展這個框架來實現(xiàn)自定義的PopupBox。
本文配套源碼