在試用了一個開源的輕量級2D引擎庫SonicUI後,感覺它的執行效率不錯,因此對它的運行機制產生了興趣並做了一個簡要的分析。按照我的習慣思路都是針對一個特例來進行分析,這樣能夠起到抽繭剝絲的作用,在對分析對象有一個感性認識後再深入瞭解其它細節。
========================
一、唯一全域變數:CSonicUI,其它控制項為託管對象(IMAGE、WND_EFFECT、STRING、SCROLL_BAR、PAINT、ANIMATION、SKIN幾種)由CSonicUI執行個體負責管理
二、繪圖基礎:建立一個標準視窗(訊息函數WndProc),SonicWndProc為CSonicUI替換主視窗訊息函數(Attach/Detach將託管對象記錄並與視窗關聯)。前者處理WM_COMMAND、WM_ERASEBKGND、WM_DESTROY、WM_PAINT(此訊息中阻塞方式通知InternalWndProc完成實際繪圖工作);後者先預先處理WM_PAINT(僅記錄wParam中的PaintDC)、WM_ERASEBKGND(擷取無效地區大小)、WM_DRAWITEM、WM_NCDESTROY,然後分析所有託管對象(IMAGE、WND_EFFECT、STRING、SCROLL_BAR、PAINT、ANIMATION、SKIN幾種)並進行處理。另有一個與CSonicUI執行個體對象關聯的1X1彈出屬性視窗(訊息函數InternalWndProc)處理WM_TIMER、WM_SONIC_INTERNAL_EVENT、WM_SONIC_DETACH
三、一些技巧
1、如在訊息處理函數(如按鈕滑鼠按下事件)中商務邏輯處理較耗時,就會造成不能及時處理其它訊息(如按鈕滑鼠抬起事件)或不能及時重新整理(更嚴重可能還會造成多個無效地區合并),因此需要SonicWndProc先記錄繪圖DC及無效地區後再由WndProc用自訂訊息(WM_SONIC_INTERNAL_EVENT)通知另外一個視窗(InternalWndProc)來完成實際繪圖工作(對於需要即時重新整理的如按鈕控制項用SendMessage,否則用PostMessage)
2、訊息機制類似QT的訊號槽機制,QT進行事件和槽串連的函數原型如下
bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
四、滑鼠移進按鈕(CSonicString對象執行個體)流程
1、CSonicUI::SonicWndProc(WM_MOUSEMOVE)
=>CSonicString::MessageFilter
=>CSonicString::OnWndMsg
=>CSonicString::OnMouseMove
=>ISonicBaseData::FireDelegate(DELEGATE_EVENT_MOUSEENTER)
=>PostMessage(WM_SONIC_INTERNAL_EVENT, wParam=ISonicPaint執行個體對象指標, lParam=DELEGATE_EVENT_MOUSEENTER)
註:CSonicUI::InternalWndPro響應訊息後調用ISonicBaseData::OnDelegate時,因為CSonicString沒有託管DELEGATE_EVENT_MOUSEENTER事件(託管了DELEGATE_EVENT_PAINT事件),相應的ISonicBaseData::m_mapDelegate表中也無事件函數並直接返回。因為是非同步作業這個可能在第2步前或後執行
2、CSonicString::ChangeStatus(同步執行第一步後續操作)
=>CSonicString::KillInternalTimer
=>CSonicString::ForceRedraw
=>CSonicString::Redraw
=>CSonicPaint::Redraw
=>CSonicUI::Redraw
=>InvalidateRect
3、CSonicUI::SonicWndPro(WM_PAINT)
註:CSonicString對象不處理此訊息,直接轉下一步
4、CallWindowProc(OldProc)
註:在WndProc中對WM_PAINT有一個技巧,用ApiHook的方式替換掉了BeginPaint和EndPaint
註:BeginPaint會發送一個WM_ERASEBKGND訊息給視窗將無效地區刷成背景色
5、SonicUIDemo!WndProc(WM_PAINT)
=>CSonicString::TextOutA
=>CSonicPaint::Draw
=>CSonicPaint::InternalDraw
=>ISonicBaseData::FireDelegate(DELEGATE_EVENT_PAINT)
=>SendMessage(WM_SONIC_INTERNAL_EVENT, wParam=ISonicPaint執行個體對象指標, lParam=DELEGATE_EVENT_PAINT)
6、SonicUI::InternalWndPro(非同步執行第一步或同步執行第五步後續操作)
=>ISonicBaseData::OnDelegate
=>CSonicString::OnRender
=>CSonicString::Render
=>CSonicString::RendImage
=>CSonicPaint::Draw
=>CSonicPaint::InternalDraw
=>ISonicBaseData::FireDelegate(DELEGATE_EVENT_PAINT)
=>SendMessage(WM_SONIC_INTERNAL_EVENT, wParam=ISonicPaint執行個體對象指標, lParam=DELEGATE_EVENT_PAINT)
註:在ISonicBaseData::OnDelegate中有一個技巧,用彙編計算託管對象的託管渲染函數地址,在這一步是CSonicString::OnRender畫hover狀態圖,在下一步則是CSonicString::RenderText畫tooltip文字及背景
CSonicString有兩個ISonicPaint受管理的執行個體對象m_pOrigin和m_TextCache分別用於繪圖和文本,
7、CSonicUI::InternalWndPro(同步執行第六步後續操作)
=>ISonicBaseData::OnDelegate
=>CSonicString::RenderText
此處畫tooptip文字及背景色
8、END
五、滑鼠移出按鈕流程
同上
六、主要資料結構
七、有關髒矩形處理的類CSonicPaint,由CSonicUI::CreatePaint建立