1、概述react
µC/GUI的窗口重繪是學習者理解窗口工做原理和應用窗口操做的重點。µC/GUI的窗口重繪引入了回調機制,回調機制能夠實現圖形系統調用用戶的代碼,因爲圖形系統使用了剪切算法,使得屏幕重繪的效率和重繪的操做都大大提升。本文主要分析µC/GUI重繪窗口的過程,使學習者理解窗口的回調機制,爲進一步的應用窗口操做打下一個好的基礎。算法
回調機制後面的哲學
µC/GUI 爲窗口和窗口對象(控件)提供的回調機制實質是一個事件驅動系統。正如在大多數視窗系統中同樣,原則是控制流程不僅是從用戶程序到圖形系統(用戶程序調用圖形系統函數來更新窗口),並且能夠從用戶程序到圖形系統,同時也從圖形系統回到用戶程序,意思是圖形系統也能夠調用用戶程序提供的回調函數來達到更新窗口的目的。這種機制——常常表現好萊塢法則的特色(「不要打電話給咱們,咱們會打電話給大家!」)——主要是視窗管理器爲了啓動窗口重繪的須要。與傳統程序比較有差別,但它使對視窗管理器的無效邏輯開發成爲可能。windows
2、與窗口有關的結構體和變量 app
一、WM_Objide
/* 窗體管理結構體 共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個字節 };
二、WM_MESSAGE函數
struct WM_MESSAGE { int MsgId; //信息的類型 WM_HWIN hWin; //信息的接收窗口 WM_HWIN hWinSrc; //發送信息的源窗口 union { const void* p; int v; GUI_COLOR Color; } Data; };
Messages Ids學習
/********************************************************************* * * Messages Ids The following is the list of windows messages. */ #define WM_CREATE 0x0001 /* The first message received, right after client has actually been created */ #define WM_MOVE 0x0003 /* window has been moved (Same as WIN32) */ #define WM_SIZE 0x0005 /* Is sent to a window after its size has changed (Same as WIN32, do not change !) */ #define WM_DELETE 11 /* Delete (Destroy) command: This tells the client to free its data strutures since the window it is associates with no longer exists.*/ #define WM_TOUCH 12 /* Touch screen message */ #define WM_TOUCH_CHILD 13 /* Touch screen message to ancestors */ #define WM_KEY 14 /* Key has been pressed */ #define WM_PAINT 0x000F /* Repaint window (because content is (partially) invalid */ #if GUI_SUPPORT_MOUSE #define WM_MOUSEOVER 16 /* Mouse has moved, no key pressed */ #define WM_MOUSEOVER_END 18 /* Mouse has moved, no key pressed */ #endif #define WM_PID_STATE_CHANGED 17 /* Pointer input device state has changed */ #define WM_GET_INSIDE_RECT 20 /* get inside rectangle: client rectangle minus pixels lost to effect */ #define WM_GET_ID 21 /* Get id of widget */ #define WM_SET_ID 22 /* Set id of widget */ #define WM_GET_CLIENT_WINDOW 23 /* Get window handle of client window. Default is the same as window */ #define WM_CAPTURE_RELEASED 24 /* Let window know that mouse capture is over */ #define WM_INIT_DIALOG 29 /* Inform dialog that it is ready for init */ #define WM_SET_FOCUS 30 /* Inform window that it has gotten or lost the focus */ #define WM_GET_ACCEPT_FOCUS 31 /* Find out if window can accept the focus */ #define WM_NOTIFY_CHILD_HAS_FOCUS 32 /* Sent to parent when child receives / loses focus */ #define WM_NOTIFY_OWNER_KEY 33 /* Some widgets (e.g. listbox) notify owner when receiving key messages */ #define WM_GET_BKCOLOR 34 /* Return back ground color (only frame window and similar) */ #define WM_GET_SCROLL_STATE 35 /* Query state of scroll bar */ #define WM_SET_SCROLL_STATE 36 /* Set scroll info ... only effective for scrollbars */ #define WM_NOTIFY_CLIENTCHANGE 37 /* Client area may have changed */ #define WM_NOTIFY_PARENT 38 /* Notify parent. Information is detailed as notification code */ #define WM_NOTIFY_PARENT_REFLECTION 39 /* Notify parent reflection. Sometimes send back as a result of the WM_NOTIFY_PARENT message to let child react on behalf of its parent. Information is detailed as notification code */ #define WM_NOTIFY_ENABLE 40 /* Enable or disable widget */ #define WM_NOTIFY_VIS_CHANGED 41 /* Visibility of a window has or may have changed */ #define WM_HANDLE_DIALOG_STATUS 42 /* Set or get dialog status */ #define WM_GET_RADIOGROUP 43 /* Send to all siblings and children of a radio control when selection changed */ #define WM_MENU 44 /* Send to owner window of menu widget */ #define WM_SCREENSIZE_CHANGED 45 /* Send to all windows when size of screen has changed */ #define WM_TIMER 0x0113 /* Timer has expired (Keep the same as WIN32) */ #define WM_WIDGET 0x0300 /* 256 messages reserved for Widget messages */ #define WM_USER 0x0400 /* Reserved for user messages ... (Keep the same as WIN32) */ /********************************************************************* * * Notification codes * * The following is the list of notification codes send * with the WM_NOTIFY_PARENT message */ #define WM_NOTIFICATION_CLICKED 1 #define WM_NOTIFICATION_RELEASED 2 #define WM_NOTIFICATION_MOVED_OUT 3 #define WM_NOTIFICATION_SEL_CHANGED 4 #define WM_NOTIFICATION_VALUE_CHANGED 5 #define WM_NOTIFICATION_SCROLLBAR_ADDED 6 /* Scroller added */ #define WM_NOTIFICATION_CHILD_DELETED 7 /* Inform window that child is about to be deleted */ #define WM_NOTIFICATION_GOT_FOCUS 8 #define WM_NOTIFICATION_LOST_FOCUS 9 #define WM_NOTIFICATION_SCROLL_CHANGED 10 #define WM_NOTIFICATION_WIDGET 11 /* Space for widget defined notifications */ #define WM_NOTIFICATION_USER 16 /* Space for application (user) defined notifications */
三、調試時觀察的全局變量spa
WM__NumWindows、WM__NumInvalidWindows、WM__FirstWin、NextDrawWin。 調試
3、窗口重繪的過程 rest
在分析窗口重繪的過程以前,咱們先看一下怎麼令一個窗口進行重繪呢?
要使一個窗口重繪,須要爲這個窗口指定回調函數,能夠在建立的時候指定或者利用函數WM_SetCallback進行指定。而後,令這個窗口無效,窗口無效可能只是窗口的一部分區域或者所有區域。最後,調用函數GUI_Exec()或者WM_Exec()來進行重繪,重繪的時候會調用咱們以前定義的回調函數。
可見,咱們設置了回調函數,包括使窗口無效都不能使屏幕發生改變,只有當執行了GUI_Exec()函數後,才能將以前的須要改變展示在屏幕上。
一、過程概覽
GUI_Exec()-->
GUI_Exec1()-->
WM_Exec()-->
WM_Exec1()-->
_DrawNext-->
WM__Paint-->
WM_PaintWinAndOvlays-->
__Paint1-->
WM_SendMessage-->
WmCallback(回調函數)
二、GUI_Exec()
int GUI_Exec(void) { int r = 0; while (GUI_Exec1()) { r = 1; /* We have done something */ } return r; }
循環執行屢次,直到把須要派發的消息和須要更新的窗口所有完成,才結束此函數。從GUI_Exec()到WM_Exec1(),能夠看到有三個函數都有相似「int r = 0; xxx(xxx_Exec1()) {r = 1; }」這樣的代碼。對於GUI_Exec()中的while循環,只有當GUI_Exec1()返回0的時候,才能結束這個函數。而GUI_Exec1()返回0的時候,對應的是(*GUI_pfTimerExec)()和WM_Exec()都返回0,這就意味着這兩個函數裏邊包含的工做已經徹底完成。
三、 GUI_Exec1()
int GUI_Exec1(void) { int r = 0; /* Execute background jobs */ if (GUI_pfTimerExec) { if ((*GUI_pfTimerExec)()) { r = 1; /* We have done something */ } } #if GUI_WINSUPPORT /* If 0, WM will not generate any code */ if (WM_Exec()) r = 1; #endif return r; }
返回1,標誌着目前GUI還有未處理的消息。返回0,標誌着GUI全部的消息都已經處理完畢。
四、WM_Exec()
int WM_Exec(void) { int r = 0; while (WM_Exec1()) { r = 1; /* We have done something */ } return r; }
五、WM_Exec1()
返回1,標誌着目前還有未處理的窗口。返回0,標誌着全部的窗口都已經處理完畢。
int WM_Exec1(void) { /* Poll PID if necessary */ if (WM_pfPollPID) { WM_pfPollPID(); } if (WM_pfHandlePID) { if (WM_pfHandlePID()) return 1; /* We have done something ... */ } if (WM_IsActive) { if (GUI_PollKeyMsg()) { return 1; /* We have done something ... */ } } #ifdef WIN32 if (WM_PollSimMsg()) { return 1; /* We have done something ... */ } #endif /* * 此部分代碼是重繪的代碼,每一次只能重繪一個窗口,因爲此函數被上一層的while循環調用 * 因此會一直重繪,直到沒有再須要重繪的窗口,也就是WM__NumInvalidWindows=0。 * 經過開闢在動態內存中的窗口管理鏈表,不斷地搜尋每個無效窗口進行重繪,直到沒有須要重繪的窗口, * 能夠說WM__NumInvalidWindows控制了循環的次數。 */ if (WM_IsActive && WM__NumInvalidWindows) { WM_LOCK(); _DrawNext(); WM_UNLOCK(); return 1; //若是重繪尚未結束,返回1,證實還有須要乾的事,就不退出外邊的循環 } return 0; //若是全部的操做都完成了,返回0,就退出外邊的循環 }
返回1,標誌着目前還有未處理的窗口。返回0,標誌着全部的窗口都已經處理完畢。
六、_DrawNext
static void _DrawNext(void) { int UpdateRem = 1; /* 若是下一個重繪的窗口爲空,那麼就將iwin設置爲第一個窗口的句柄 */ WM_HWIN iWin = (NextDrawWin == WM_HWIN_NULL) ? WM__FirstWin : NextDrawWin; GUI_CONTEXT ContextOld; GUI_SaveContext(&ContextOld); //重繪一個窗口的時候,會修改GUI_Context,咱們但願在重繪結束的時候 //恢復到原來的樣子,因此要保存這個全局變量 /* Make sure the next window to redraw is valid */ //這個for循環的目的就是肯定必定要重繪一個窗口,若是iWin剛開始爲有效窗口,那麼就循環找到 //第一個無效窗口,並進行重繪。它並不會把多個無效窗口進行重繪。 for (; iWin && UpdateRem; ) { //確保iwin是有效的 WM_Obj* pWin = WM_H2P(iWin); //得到當前須要操做的窗口在動態內存中的管理節點地址 if (WM__Paint(iWin, pWin)) { //重繪(注意只有是無效的窗口才會進行重繪,而有效的窗口是不會重繪的) UpdateRem--; /* Only the given number of windows at a time ... */ } iWin = pWin->hNextLin; //iwin指向下一個窗體的句柄 } NextDrawWin = iWin; //下一個須要重繪的窗口WM_Exec()-->WM_Exec1()-->_DrawNext //每次只能重繪一個窗口,因此有多個須要重繪的窗口,咱們但願它能繼續接着上一次重繪窗口的後邊 //繼續重繪,而非從頭再來 GUI_RestoreContext(&ContextOld); //重繪結束的時候,恢復GUI_Context }
_DrawNext會遍歷整個窗口鏈表,從最底層的桌面窗口開始到最頂層的窗口,根據須要(是否設置爲無效、是否設置爲可視等)進行重繪操做。
七、WM__Paint()
返回值,1表明有窗口被重繪了,0表明沒有窗口被重繪。
/********************************************************************* * * WM__Paint Returns: 1: a window has been redrawn 有窗口被重繪了 0: No window has been drawn 沒有窗口被重繪了 */ int WM__Paint(WM_HWIN hWin, WM_Obj* pWin) { int Ret = 0; if (pWin->Status & WM_SF_INVALID) { //首先判斷窗口是無效的,纔會進行重繪 if (pWin->cb) { //若是窗口有回調函數會調用它的回調函數,若是沒有則不處理 if (WM__ClipAtParentBorders(&pWin->InvalidRect, hWin)) { WM_PAINTINFO Info; Info.hWin = hWin; WM_SelectWindow(hWin); //由於要對窗口進行重繪,因此要先選擇窗口做爲活動窗口 #if GUI_SUPPORT_MEMDEV Info.pWin = NULL; /* 'Invalidate' the window pointer, because it can become invalid through the creation of a memory device */ if (pWin->Status & WM_SF_MEMDEV) { int Flags; GUI_RECT r = pWin->InvalidRect; Flags = (pWin->Status & WM_SF_HASTRANS) ? GUI_MEMDEV_HASTRANS : GUI_MEMDEV_NOTRANS; /* * Currently we treat a desktop window as transparent, because per default it does not repaint itself. */ if (pWin->hParent == 0) { Flags = GUI_MEMDEV_HASTRANS; } GUI_MEMDEV_Draw(&r, _cbPaintMemDev, &Info, 0, Flags); } else #endif { Info.pWin = pWin; WM__PaintWinAndOverlays(&Info); //對窗口進行重繪 Ret = 1; /* Something has been done */ } } } pWin->Status &= ~WM_SF_INVALID; //窗口已經重繪,就將其無效標誌清除 if (pWin->Status & WM_CF_MEMDEV_ON_REDRAW) { pWin->Status |= WM_CF_MEMDEV; } WM__NumInvalidWindows--; //窗口已經重繪,就將無效的窗口數減1 } return Ret; /* Nothing done */ }
八、WM_PaintWinAndOvlays
/********************************************************************* * * WM__PaintWinAndOverlays * * Purpose * Paint the given window and all overlaying windows * (transparent children and transparent top siblings) */ void WM__PaintWinAndOverlays(WM_PAINTINFO* pInfo) { WM_HWIN hWin; WM_Obj* pWin; hWin = pInfo->hWin; pWin = pInfo->pWin; if (!pWin) { pWin = WM_H2P(hWin); } #if WM_SUPPORT_TRANSPARENCY if ((pWin->Status & (WM_SF_HASTRANS | WM_SF_CONST_OUTLINE)) != WM_SF_HASTRANS) { #endif _Paint1(hWin, pWin); /* Draw the window itself */ //重繪窗口本身 #if GUI_SUPPORT_MEMDEV /* Within the paint event the application is alowed to deal with memory devices. So the pointer(s) could be invalid after the last function call and needs to be restored. */ pWin = WM_H2P(hWin); #endif #if WM_SUPPORT_TRANSPARENCY } if (WM__TransWindowCnt != 0) { _PaintTransChildren(hWin, pWin); /* Draw all transparent children */ #if GUI_SUPPORT_MEMDEV /* Within the paint event the application is alowed to deal with memory devices. So the pointer(s) could be invalid after the last function call and needs to be restored. */ pWin = WM_H2P(hWin); #endif _PaintTransTopSiblings(hWin, pWin); /* Draw all transparent top level siblings */ } #endif }
九、__Paint1()
static void _Paint1(WM_HWIN hWin, WM_Obj* pWin) { int Status = pWin->Status; /* Send WM_PAINT if window is visible and a callback is defined */ if ((pWin->cb != NULL) && (Status & WM_SF_ISVIS)) {//若是是可視窗口並且窗口的回調函數定義了,纔會重繪 WM_MESSAGE Msg; WM__PaintCallbackCnt++; if (Status & WM_SF_LATE_CLIP) { Msg.hWin = hWin; Msg.MsgId = WM_PAINT; //發送重繪消息 Msg.Data.p = (GUI_RECT*)&pWin->InvalidRect; WM_SetDefault(); WM__SendMessage(hWin, &Msg); } else { /* 這是一個循環,不斷的給須要重繪的窗口發送消息進行重繪 */ WM_ITERATE_START(&pWin->InvalidRect) { //須要注意的是:外來發送重繪的消息只有一個 //這裏將消息分紅幾回發送,每次發送只重繪一部分,當發送完成的時候 //完整的重繪效果纔會展示 Msg.hWin = hWin; //接收消息的窗口 Msg.MsgId = WM_PAINT; //重繪消息 Msg.Data.p = (GUI_RECT*)&pWin->InvalidRect; WM_SetDefault(); WM__SendMessage(hWin, &Msg); //發送消息,啓動回調函數 } WM_ITERATE_END(); } WM__PaintCallbackCnt--; } }
十、WM__SendMessage()
void WM__SendMessage(WM_HWIN hWin, WM_MESSAGE* pMsg) { static int _EntranceCnt; WM_Obj* pWin; if (_EntranceCnt < GUI_MAX_MESSAGE_NESTING) { pWin = WM_HANDLE2PTR(hWin); //獲取操做窗口句柄對應的動態內存地址 pMsg->hWin = hWin; //打包消息 if (pWin->cb != NULL) { _EntranceCnt++; (*pWin->cb)(pMsg); //調用回調函數對消息進行處理 _EntranceCnt--; } else { WM_DefaultProc(pMsg); //若是沒有回調函數,信息也會默認處理 } } #if GUI_DEBUG_LEVEL >= GUI_DEBUG_LEVEL_CHECK_PARA else { GUI_DEBUG_ERROROUT("Max. message nesting exceeded, Message skipped."); } #endif }
發送消息的時候,會調用窗口的回調函數,從而對消息進行處理。
參考資料:《uCGUI中文手冊》