著名的千千靜聽(tīng)音樂(lè)播放器,其界面簡(jiǎn)潔優(yōu)雅、美觀大方,特別是它那種幾個(gè)窗口像磁石般相互吸引,當(dāng)拖動(dòng)主窗口時(shí),粘在一起的其它窗口會(huì)跟隨著一起移動(dòng),當(dāng)拖動(dòng)其它窗口時(shí),又能脫離不粘在一起,這種窗口效果讓用戶(hù)操作方便,省心省力。為描述方便,本文稱(chēng)這種效果為多窗口的組合分離,它的主要特點(diǎn)是僅用鼠標(biāo)任意移動(dòng)窗口,就可組合或分離,當(dāng)組合在一起時(shí),移動(dòng)某窗口(如主窗口,暫稱(chēng)為老板窗口)能整體移動(dòng),移動(dòng)其口窗口(非老板窗口,暫稱(chēng)為工人窗口)又能將自己脫離出來(lái)。近來(lái)由于工作需要實(shí)現(xiàn)了類(lèi)似于此的窗口效果,經(jīng)過(guò)幾天的測(cè)試,終于穩(wěn)定。在開(kāi)發(fā)過(guò)程中,考慮了以下幾個(gè)問(wèn)題:
(1) 組合分離的條件如何決定判斷。
(2) 當(dāng)窗口大小改變時(shí),包括最小化,最大化,縮放窗口等,如何保證不影響組合分離,能正常整體移動(dòng)。
(3) 窗口個(gè)數(shù)是可變的,當(dāng)新建或銷(xiāo)毀窗口時(shí),如何保證不影響組合分離,能正常整體移動(dòng)(千千靜聽(tīng)窗口個(gè)數(shù)是有限的,而且可能只是隱藏窗口)。
(4) 采用什么數(shù)據(jù)結(jié)構(gòu)較好,如何維護(hù)任意兩個(gè)窗口間的距離關(guān)系(相交或相切視為組合,相離視為分離)。
(5) 當(dāng)拖動(dòng)老板窗口時(shí),如何拖動(dòng)與其組合的所有窗口,關(guān)鍵是如何得到所有與其組合的窗口列表。
(6) 如何針對(duì)這種效果設(shè)計(jì)一個(gè)通用的組件類(lèi),只需調(diào)用幾個(gè)方法便可搞定。
針對(duì)以上問(wèn)題,主要思路是視屏幕上任意多個(gè)窗口為頂點(diǎn),以其窗口矩形中心點(diǎn)來(lái)看待這個(gè)窗口,如果任意兩個(gè)窗口間關(guān)系為組合,則視這兩個(gè)頂點(diǎn)間是相通的,即兩個(gè)頂點(diǎn)存在邊。如果為分離,則視兩頂點(diǎn)間是不通的,即兩頂點(diǎn)不存邊。因此可以用無(wú)向圖來(lái)存儲(chǔ)窗口和關(guān)系,為簡(jiǎn)單起見(jiàn),我用的是鄰接矩陣,問(wèn)題(4)得以解決。既然用鄰接矩陣來(lái)存儲(chǔ),那么如何得到所有與老板窗口相關(guān)的組合窗口呢?由于實(shí)際多個(gè)窗口在移動(dòng)過(guò)程中,會(huì)改變其組合分離關(guān)系,這就會(huì)得到多個(gè)無(wú)向圖的連通分量,而我們需要的是包含老板窗口的那一個(gè)連通分量,因此可以用DFS深度搜索遍歷這個(gè)無(wú)向圖連通分量,起始頂點(diǎn)是老板窗口,遍歷完后就會(huì)得所有與其組合的窗口列表,問(wèn)題(5)得以解決。現(xiàn)在討論問(wèn)題(1),這里有個(gè)細(xì)節(jié)問(wèn)題就是組合分離的條件判斷有兩種情況,一是當(dāng)移動(dòng)窗口時(shí)的條件,稱(chēng)為條件1,因?yàn)閷?shí)際向一個(gè)窗口A移入另一個(gè)窗口B時(shí),要達(dá)到還沒(méi)有接近窗口A時(shí)便一下子靠近A就像被A吸引的效果,當(dāng)移出B時(shí)還沒(méi)完全移到A窗口外面時(shí)便一下子遠(yuǎn)離就像被A排斥的效果。二是當(dāng)大小改變時(shí)的條件,稱(chēng)為條件2,這個(gè)不同于條件1,因?yàn)樗恍枰欠N吸引排斥的效果,也沒(méi)必要,這個(gè)條件2就是簡(jiǎn)單的判斷A和B矩形是否相交,API函數(shù)IntersectRect即可完成這一判斷。條件1的判斷如下圖所示:
在B向A移入過(guò)程中,當(dāng)B的中心點(diǎn)在矩形left,top,right,bottom范圍內(nèi),可認(rèn)為是發(fā)生組合,實(shí)現(xiàn)吸引效果;當(dāng)在center矩形內(nèi),認(rèn)為是已經(jīng)組合了;同理,B向A移出過(guò)程中,當(dāng)B的中心點(diǎn)在矩形left,top,right,bottom范圍內(nèi),可認(rèn)為是發(fā)生分離,實(shí)現(xiàn)排斥效果。當(dāng)都不在left,top,right,bottom,center矩形范圍時(shí),認(rèn)為是已經(jīng)分離了。至此,問(wèn)題(1)得到解決。當(dāng)窗口大小改變時(shí),需要更新鄰接矩陣反映窗口間關(guān)系的變化,而后更新組合窗口列表,組合窗口列表的計(jì)算依賴(lài)于鄰接矩陣,運(yùn)用DFS算法來(lái)更新,這在WM_SIZE消息事件處理內(nèi)完成,問(wèn)題(2)得到解決。當(dāng)新建窗口時(shí),需要向無(wú)向圖中增加(窗口)頂點(diǎn),擴(kuò)充鄰接矩陣以備存儲(chǔ)與其它窗口的關(guān)系;當(dāng)銷(xiāo)毀窗口時(shí),需要從無(wú)向圖中刪除對(duì)應(yīng)的頂點(diǎn),而后從鄰接矩陣中刪除對(duì)應(yīng)的關(guān)系,問(wèn)題(3)得到解決。
上述問(wèn)題(1)--(5)都已分析并得到解決,總的來(lái)說(shuō),就是以數(shù)據(jù)結(jié)構(gòu)中無(wú)向圖的觀點(diǎn)和算法來(lái)建模解決這些問(wèn)題的,特別是運(yùn)用到了DFS搜索算法來(lái)重建已組合的所有窗口列表,只有這樣,在移動(dòng)老板窗口過(guò)程中,才能保證其它窗口跟隨著一起移動(dòng)。接下來(lái)就是最后一個(gè)問(wèn)題,也就是怎么封裝設(shè)計(jì)組件類(lèi),以達(dá)到方便應(yīng)用的目的,綜上所述,設(shè)計(jì)接口方法與以下窗口4種消息相關(guān):
1) 創(chuàng)建窗口發(fā)生的消息,如WM_CREATE,WM_INITDIALOG等。
2) 關(guān)閉或銷(xiāo)毀窗口發(fā)生的消息,如WM_CLOSE,WM_DESTROY等。
3) 窗口大小改變后消息,WM_SIZE。
4) 窗口移動(dòng)中消息,WM_MOVING。
另外提供一個(gè)設(shè)置獲取老板窗口的方法,在應(yīng)用程序中,只需在窗口4種消息處理內(nèi)調(diào)用以上對(duì)應(yīng)4個(gè)方法即可實(shí)現(xiàn)多窗口組合分離的效果,注意該類(lèi)沒(méi)有考慮多線程,因此是非安全的,適用于多窗口屬于同一線程內(nèi)的情況。類(lèi)聲明如下
1
class CWndMagnet
2

{
3
public:
4
CWndMagnet();
5
virtual ~CWndMagnet();
6
7
public:
8
void SetLeadWindow(HWND hWnd)
{ m_hLead = hWnd; }
9
HWND GetLeadWindow() const
{ return m_hLead; }
10
11
void AddMagnetWnd(HWND hWnd);
12
void RemoveMagnetWnd(HWND hWnd);
13
void OnLButtonDown(HWND hWnd);
14
void OnNcLButtonDown(HWND hWnd);
15
void OnMoving(HWND hWnd, LPRECT lpRect);
16
void OnSize(HWND hWnd, UINT uType);
17
18
protected:
19
void MoveLeadWndSet(HWND hWnd, LPCRECT lpRect);
20
void UpdateLeadWndSet(HWND hWnd, LPCRECT lpRect = 0);
21
void DeleteMagWnd(HWND hWnd);
22
void Add2DMatrix();
23
void Delete2DMatrix(HWND hWnd);
24
void Update2DMatrix(HWND hWnd, LPRECT lpRect = 0);
25
26
private:
27
int GetFirstNeighbor(int v);
28
int GetNextNeighbor(int v, int w);
29
void DFS(int v, std::vector<bool>& vecVisited, std::vector<int>& vecNeighbor);
30
31
private:
32
static const int s_c_iThreshold = 10; /**////< 偏移閥值
33
HWND m_hLead; ///< 老板窗口
34
std::map<HWND,POINT> m_map_leadWnd; ///< 粘合窗口列表
35
std::map<HWND,int> m_map_magWnd; ///< 需要組合分離的窗口列表
36
std::vector<std::vector<bool> > m_vec_2DMatrix; ///< 表示任意兩個(gè)窗口間相交或相切的鄰接矩陣
37
38
};
posted on 2011-07-04 11:14
春秋十二月 閱讀(2833)
評(píng)論(0) 編輯 收藏 引用 所屬分類(lèi):
C/C++