一、相關結構體和變量
窗口管理結構體
/* 窗口管理結構體 共30個字節 */ struct WM_Obj { GUI_RECT Rect; //窗口尺寸(x0,y0,x1,y1) 8個字節 GUI_RECT InvalidRect; //無效區域(x0,y0,x1,y1) 8個字節 WM_CALLBACK* cb; //回調函數 4個字節 WM_HWIN hNextLin; //指向鏈表中的下一個窗口 2個字節 WM_HWIN hParent; //當前窗口的父窗口 2個字節 WM_HWIN hFirstChild; //當前窗口的第一個子窗口 2個字節 WM_HWIN hNext; //下一個兄弟窗口 2個字節 U16 Status; //標志位 2個字節 };
窗口創建的標志
#define WM_CF_HASTRANS (1<<0) /* Has transparency. Needs to be defined for windows which do not fill the entire section of their (client) rectangle. */ #define WM_CF_HIDE (0<<1) /* Hide window after creation (default !) */ #define WM_CF_SHOW (1<<1) /* Show window after creation */ #define WM_CF_MEMDEV (1<<2) /* Use memory device for redraws */ #define WM_CF_STAYONTOP (1<<3) /* Stay on top */ #define WM_CF_DISABLED (1<<4) /* Disabled: Does not receive PID (mouse & touch) input */ /* Create only flags ... Not available as status flags */ #define WM_CF_ACTIVATE (1<<5) /* If automatic activation upon creation of window is desired */ #define WM_CF_FGND (0<<6) /* Put window in foreground after creation (default !) */ #define WM_CF_BGND (1<<6) /* Put window in background after creation */ /* Anchor flags */ #define WM_CF_ANCHOR_RIGHT (1<<7) /* Right anchor ... If parent is resized, distance to right will remain const (left is default) */ #define WM_CF_ANCHOR_BOTTOM (1<<8) /* Bottom anchor ... If parent is resized, distance to bottom will remain const (top is default) */ #define WM_CF_ANCHOR_LEFT (1<<9) /* Left anchor ... If parent is resized, distance to left will remain const (left is default) */ #define WM_CF_ANCHOR_TOP (1<<10) /* Top anchor ... If parent is resized, distance to top will remain const (top is default) */ #define WM_CF_CONST_OUTLINE (1<<11) /* Constant outline. This is relevant for transparent windows only. If a window is transparent and does not have a constant outline, its background is invalided instead of the window itself. This causes add. computation time when redrawing. */ #define WM_CF_LATE_CLIP (1<<12) #define WM_CF_MEMDEV_ON_REDRAW (1<<13) #define WM_CF_RESERVED3 (1<<14) #define WM_CF_RESERVED4 (1<<15)
WM_CF_SHOW、WM_CF_STAYONTOP、WM_CF_HIDE、WM_CF_ACTIVATE這幾個標志是經常用到的。
二、窗口創建的過程分析
1、WM_CreateWindowAsChild
WM_HWIN WM_CreateWindowAsChild( int x0, int y0, int width, int height ,WM_HWIN hParent, U16 Style, WM_CALLBACK* cb ,int NumExtraBytes) { WM_Obj* pWin; WM_HWIN hWin; WM_ASSERT_NOT_IN_PAINT(); //斷言,這里沒有使用 WM_LOCK(); Style |= WM__CreateFlags; //給窗口的標志增加一個創建標志 /* Default parent is Desktop 0 */ if (!hParent) { //如果不存在父窗口,比如說桌面窗口 if (WM__NumWindows) { //創建桌面窗口,這個不會執行的 #if GUI_NUM_LAYERS == 1 hParent = WM__ahDesktopWin[0]; //如果用戶沒有指定當前創建窗口的父窗口,而且該窗口 //又不是桌面窗口,默認的將桌面窗口作為其父窗口 #else hParent = WM__ahDesktopWin[GUI_Context.SelLayer]; #endif } } if (hParent == WM_UNATTACHED) { hParent = WM_HWIN_NULL; } if (hParent) { WM_Obj* pParent = WM_H2P(hParent); x0 += pParent->Rect.x0; y0 += pParent->Rect.y0; if (width==0) { width = pParent->Rect.x1 - pParent->Rect.x0+1; } if (height==0) { height = pParent->Rect.y1 - pParent->Rect.y0+1; } } if ((hWin = (WM_HWIN) GUI_ALLOC_AllocZero(NumExtraBytes + sizeof(WM_Obj))) == 0) { GUI_DEBUG_ERROROUT("WM_CreateWindow: No memory to create window");
//如果沒有空間來創建需要的動態內存塊 } else { //申請動態內存成功 WM__NumWindows++; //保存系統總窗口數目的計數器加1 pWin = WM_H2P(hWin); //計算獲取動態內存數據區的地址 /* 向動態內存區寫入當前窗口的參數 */ pWin->Rect.x0 = x0; pWin->Rect.y0 = y0; pWin->Rect.x1 = x0 + width - 1; pWin->Rect.y1 = y0 + height - 1; pWin->cb = cb; //保存回調函數 /* Copy the flags which can simply be accepted */ pWin->Status |= (Style & (WM_CF_SHOW | WM_SF_MEMDEV | WM_CF_MEMDEV_ON_REDRAW | WM_SF_STAYONTOP | WM_CF_DISABLED | WM_SF_CONST_OUTLINE | WM_SF_HASTRANS | WM_CF_ANCHOR_RIGHT | WM_CF_ANCHOR_BOTTOM | WM_CF_ANCHOR_LEFT | WM_CF_ANCHOR_TOP | WM_CF_LATE_CLIP)); /* Add to linked lists */ _AddToLinList(hWin); //將窗口插入到窗口管理鏈表當中 WM__InsertWindowIntoList(hWin, hParent); //插入到父窗口管理鏈表當中 /* 根據用戶定義的窗口風格進行一些列的操作 */ /* Activate window if WM_CF_ACTIVATE is specified */ if (Style & WM_CF_ACTIVATE) { //如果帶激活標志的話,就激活窗口 WM_SelectWindow(hWin); /* This is not needed if callbacks are being used, but it does not cost a lot and makes life easier ... */ } /* Handle the Style flags, one at a time */ #if WM_SUPPORT_TRANSPARENCY if (Style & WM_SF_HASTRANS) { //透明窗口 WM__TransWindowCnt++; /* Increment counter for transparency windows */ } #endif if (Style & WM_CF_BGND) { WM_BringToBottom(hWin); } if (Style & WM_CF_SHOW) { //顯示窗口 pWin->Status |= WM_SF_ISVIS; //設置可視狀態位 WM_InvalidateWindow(hWin); //如果有顯示命令,還會設置窗口為無效,等待重繪 } WM__SendMsgNoData(hWin, WM_CREATE); //發一個創建消息,這樣創建的時候就可以在回調函數中進行處理 } WM_UNLOCK(); return hWin; }
首先根據其父窗口的坐標計算出當前窗口的坐標、高度和寬度。從動態內存區中開辟出一塊窗口管理區域,然后向其中填入當前窗口的參數值。比較重要的是接下來的兩部,將當前窗口插入到窗口管理鏈表當中以及將窗口插入到其父窗口的同胞鏈表當中。最后,如果創建的時候以顯示模式WM_CF_SHOW創建,那么要為此窗口加入了可視標志WM_SF_ISVIS,而且還要設置窗口為無效。這樣在執行GUI_Exec()或者WM_Exec()的時候就會對該窗口進行重繪。
2、_AddToLinList()
static void _AddToLinList(WM_HWIN hNew) { WM_Obj* pFirst; WM_Obj* pNew; if (WM__FirstWin) { //如果不是桌面窗口(事實上桌面窗口肯定存在了) pFirst = WM_H2P(WM__FirstWin); //首先獲取桌面窗口的動態內存地址 pNew = WM_H2P(hNew); //獲取要插入窗口的動態內存地址 /* * 桌面窗口--->最近創建的窗口1--->更早創建的窗口2~~~~~~~--->0 ==> * 桌面窗口--->當前要插入的窗口--->最近創建的窗口1--->更早創建的窗口2~~~--->0 */ pNew->hNextLin = pFirst->hNextLin; // pFirst->hNextLin = hNew; } else { WM__FirstWin = hNew; //創建桌面窗口時,將桌面窗口的句柄賦給此變量 } }
將新建窗口添加到窗口管理鏈表中。這個窗口管理鏈表是建立在uCGUI的動態內存中,利用WM_obj類型中的成員hNextLin連接成一個單向鏈表。鏈表的構建過程如下:
插入之前:桌面窗口--->最近創建的窗口1--->更早創建的窗口2~~~~~~~--->0 ==>
插入之后:桌面窗口--->當前要插入的窗口--->最近創建的窗口1--->更早創建的窗口2~~~--->0
3、WM__InsertWindowIntoList()
/********************************************************************* * * WM__InsertWindowIntoList * * Routine describtion * This routine inserts the window in the list of child windows for * a particular parent window. * The window is placed on top of all siblings with the same level. */ void WM__InsertWindowIntoList(WM_HWIN hWin, WM_HWIN hParent) { int OnTop; WM_HWIN hi; WM_Obj * pWin; WM_Obj * pParent; WM_Obj * pi; if (hParent) { //桌面窗口是不存在父窗口的 pWin = WM_H2P(hWin); //獲取當前窗口的動態內存地址 pWin->hNext = 0; //它的下一個兄弟窗口為0 pWin->hParent = hParent; //記錄它的父窗口 pParent = WM_H2P(hParent); //獲得它父窗口的動態內存地址 OnTop = pWin->Status & WM_CF_STAYONTOP; //可以用來判斷此窗口是否有在最頂層的標志 hi = pParent->hFirstChild; //父窗口的第一個子窗口 /* Put it at beginning of the list if there is no child */ if (hi == 0) { /* No child yet ... Makes things easy ! */ /* * 父窗口--->0 ====> * 父窗口--->當前窗口--->0 */ pParent->hFirstChild = hWin; //沒有子窗口,就把它作為父窗口的第一個子窗口 return; /* Early out ... We are done */ } /* Put it at beginning of the list if first child is a TOP window and new one is not */ pi = WM_H2P(hi); //獲取父窗口第一個子窗口的動態內存地址 if (!OnTop) { //如果此窗口沒有在最頂層的標志 if (pi->Status & WM_SF_STAYONTOP) { //判斷長兄是否有在最頂層的標志 /* * 父窗口--->長兄--->~~~--->0 => * 父窗口--->當前窗口--->長兄--->~~~--->0 */ pWin->hNext = hi; //當前窗口的下一個窗口指向其長兄 pParent->hFirstChild = hWin; //父窗口的第一個子窗口為當前窗口 return; /* Early out ... We are done */ } } /* 把它放在鏈表的最頂端或者在第一個“最頂層”窗口之前 */ do { WM_Obj* pNext; WM_HWIN hNext; if ((hNext = pi->hNext) == 0) { /* End of sibling list ? */ pi->hNext = hWin; /* 放在鏈表的最頂端 */ break; } pNext = WM_H2P(hNext); if (!OnTop) { //如果當前窗口沒有“在最頂層”的標志 if (pNext->Status & WM_SF_STAYONTOP) { pi->hNext = hWin; pWin->hNext = hNext; break; } } pi = pNext; } while (1); #if WM_SUPPORT_NOTIFY_VIS_CHANGED WM__NotifyVisChanged(hWin, &pWin->Rect); #endif } }
將新建窗口添加到父窗口的同胞窗口管理鏈表中。這個窗口管理鏈表是建立在uCGUI的動態內存中,利用WM_obj類型中的成員hFirstChild和hNext連接成一個單向鏈表。
插入的原則是:
1、如果父窗口沒有孩子,直接將其作為父窗口的孩子即可。
插入之前: 父窗口--->0 ====>
插入之后: 父窗口--->當前窗口--->0
2、如果父窗口有孩子,第一個孩子有“在頂層”的標志,而當前創建的窗口沒有這個標志,則將其作為其父窗口的第一個孩子。
插入之前:父窗口--->長兄--->~~~--->0 =>
插入之后:父窗口--->當前窗口--->長兄--->~~~--->0
3、如果父窗口有孩子,第一個孩子沒有“在頂層”的標志,而當前創建的窗口也沒有這個標志,則將其放在同胞鏈表中有“在頂層”標志窗口的前邊,也就是所有沒有“在頂層”標志窗口的后邊。
插入之前:
父窗口--->長兄(沒標志)--->次兄(沒標志)--->三兄(有標志)--->~~~--->0 =>
插入之后:父窗口--->長兄(沒標志)--->次兄(沒標志)--->當前窗口--->三兄(有標志)--->~~~--->0
4、如果父窗口有孩子,第一個孩子沒有“在頂層”的標志,而當前創建的窗口有這個標志,則將其放在同胞鏈表的最后邊。
插入之前:
父窗口--->長兄(沒標志)--->次兄(沒標志)--->三兄(有標志)--->~~~--->0 =>
插入之后:父窗口--->長兄(沒標志)--->次兄(沒標志)--->三兄(有標志)--->~~~--->當前窗口--->0
三、窗口順序排列分析
1、按照窗口分層的概念來說,鏈表的頭部窗口是顯示在最底層,鏈表的尾部窗口是顯示在最頂層。在進行窗口重繪的時候,正是從鏈表的頭部開始依次往尾部進行重繪。
2、子窗口相對於父窗口來說,子窗口在父窗口的頂部。
四、桌面窗口的創建
桌面窗口的創建發生在GUI_Init函數執行過程中。如果系統開啟了窗口管理功能,GUI_Init函數會調用WM_Init函數,在WM_Init初始化窗口管理器的時候,默認的會創建桌面窗口。相關代碼如下:
/********************************************************************* * * WM_Init */ void WM_Init(void) { ...... WM__ahDesktopWin[0] = WM_CreateWindow(0, 0, GUI_XMAX, GUI_YMAX, WM_CF_SHOW, cbBackWin, 0); ...... WM_InvalidateWindow(WM__ahDesktopWin[i]); ...... WM_SelectWindow(WM__ahDesktopWin[0]); }
桌面窗口默認的回調函數
static void cbBackWin( WM_MESSAGE* pMsg) { const WM_KEY_INFO* pKeyInfo; switch (pMsg->MsgId) { case WM_KEY: pKeyInfo = (const WM_KEY_INFO*)pMsg->Data.p; if (pKeyInfo->PressedCnt == 1) { GUI_StoreKey(pKeyInfo->Key); } break; case WM_PAINT: { int LayerIndex; #if GUI_NUM_LAYERS > 1 LayerIndex = _DesktopHandle2Index(pMsg->hWin); #else LayerIndex = 0; #endif if (WM__aBkColor[LayerIndex] != GUI_INVALID_COLOR) { GUI_SetBkColor(WM__aBkColor[LayerIndex]); GUI_Clear(); } } default: WM_DefaultProc(pMsg); } }
Attention:
桌面窗口的句柄默認是1,也就是說桌面窗口使用的是動態內存節點信息結構體數組的第二個元素。動態內存分配的時候,從動態內存節點信息結構體數組的第二個元素開始分配的。