(1) 組合分離的條件如何決定判斷。
(2) 當窗口大小改變時,包括最小化,最大化,縮放窗口等,如何保證不影響組合分離,能正常整體移動。
(3) 窗口個數是可變的,當新建或銷毀窗口時,如何保證不影響組合分離,能正常整體移動(千千靜聽窗口個數是有限的,而且可能只是隱藏窗口)。
(4) 采用什么數據結構較好,如何維護任意兩個窗口間的距離關系(相交或相切視為組合,相離視為分離)。
(5) 當拖動老板窗口時,如何拖動與其組合的所有窗口,關鍵是如何得到所有與其組合的窗口列表。
(6) 如何針對這種效果設計一個通用的組件類,只需調用幾個方法便可搞定。
針對以上問題,主要思路是視屏幕上任意多個窗口為頂點,以其窗口矩形中心點來看待這個窗口,如果任意兩個窗口間關系為組合,則視這兩個頂點間是相通的,即兩個頂點存在邊。如果為分離,則視兩頂點間是不通的,即兩頂點不存邊。因此可以用無向圖來存儲窗口和關系,為簡單起見,我用的是鄰接矩陣,問題(4)得以解決。既然用鄰接矩陣來存儲,那么如何得到所有與老板窗口相關的組合窗口呢?由于實際多個窗口在移動過程中,會改變其組合分離關系,這就會得到多個無向圖的連通分量,而我們需要的是包含老板窗口的那一個連通分量,因此可以用DFS深度搜索遍歷這個無向圖連通分量,起始頂點是老板窗口,遍歷完后就會得所有與其組合的窗口列表,問題(5)得以解決。現在討論問題(1),這里有個細節問題就是組合分離的條件判斷有兩種情況,一是當移動窗口時的條件,稱為條件1,因為實際向一個窗口A移入另一個窗口B時,要達到還沒有接近窗口A時便一下子靠近A就像被A吸引的效果,當移出B時還沒完全移到A窗口外面時便一下子遠離就像被A排斥的效果。二是當大小改變時的條件,稱為條件2,這個不同于條件1,因為它不需要那種吸引排斥的效果,也沒必要,這個條件2就是簡單的判斷A和B矩形是否相交,API函數IntersectRect即可完成這一判斷。條件1的判斷如下圖所示:

在B向A移入過程中,當B的中心點在矩形left,top,right,bottom范圍內,可認為是發生組合,實現吸引效果;當在center矩形內,認為是已經組合了;同理,B向A移出過程中,當B的中心點在矩形left,top,right,bottom范圍內,可認為是發生分離,實現排斥效果。當都不在left,top,right,bottom,center矩形范圍時,認為是已經分離了。至此,問題(1)得到解決。當窗口大小改變時,需要更新鄰接矩陣反映窗口間關系的變化,而后更新組合窗口列表,組合窗口列表的計算依賴于鄰接矩陣,運用DFS算法來更新,這在WM_SIZE消息事件處理內完成,問題(2)得到解決。當新建窗口時,需要向無向圖中增加(窗口)頂點,擴充鄰接矩陣以備存儲與其它窗口的關系;當銷毀窗口時,需要從無向圖中刪除對應的頂點,而后從鄰接矩陣中刪除對應的關系,問題(3)得到解決。
上述問題(1)--(5)都已分析并得到解決,總的來說,就是以數據結構中無向圖的觀點和算法來建模解決這些問題的,特別是運用到了DFS搜索算法來重建已組合的所有窗口列表,只有這樣,在移動老板窗口過程中,才能保證其它窗口跟隨著一起移動。接下來就是最后一個問題,也就是怎么封裝設計組件類,以達到方便應用的目的,綜上所述,設計接口方法與以下窗口4種消息相關:
1) 創建窗口發生的消息,如WM_CREATE,WM_INITDIALOG等。
2) 關閉或銷毀窗口發生的消息,如WM_CLOSE,WM_DESTROY等。
3) 窗口大小改變后消息,WM_SIZE。
4) 窗口移動中消息,WM_MOVING。
另外提供一個設置獲取老板窗口的方法,在應用程序中,只需在窗口4種消息處理內調用以上對應4個方法即可實現多窗口組合分離的效果,注意該類沒有考慮多線程,因此是非安全的,適用于多窗口屬于同一線程內的情況。類聲明如下
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; ///< 表示任意兩個窗口間相交或相切的鄰接矩陣
37
38
};

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38
