随笔-149  评论-223  文章-30  trackbacks-0
   著名的千千静听音乐播放器,其界面简洁优雅、美观大方,特别是它那种几个窗口像磁石般相互吸引,当拖动主窗口时,粘在一起的其它窗口会跟随着一起移动,当拖动其它窗口时,又能脱离不粘在一起,这种窗口效果让用户操作方便,省心省力。为描述方便,本文称这种效果为多窗口的组合分离,它的主要特点是仅用鼠标任意移动窗口,就可组合或分离,当组合在一起时,移动某窗口(如主窗口,暂称为老板窗口)能整体移动,移动其口窗口(非老板窗口,暂称为工人窗口)又能将自己脱离出来。近来由于工作需要实现了类似于此的窗口效果,经过几天的测试,终于稳定。在开发过程中,考虑了以下几个问题:
     (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个方法即可实现多窗口组合分离的效果,注意该类没有考虑多线程,因此是非安全的,适用于多窗口属于同一线程内的情况。类声明如下 
 1class CWndMagnet
 2{
 3public:
 4    CWndMagnet();
 5  virtual ~CWndMagnet();
 6
 7public:
 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
18protected:
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
26private:
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
31private:
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}
;
posted on 2011-07-04 11:14 春秋十二月 阅读(2755) 评论(0)  编辑 收藏 引用 所属分类: C/C++

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理