盡管SOUI已經內置了大部分常用的控件,很顯然內置控件很難滿足各種應用的形式各異的需求。
因此只有提供足夠的擴展性才能滿足真實應用場景。
除了將系統盡可能的組件化外,SOUI在控件自繪(SWindow)及繪圖對象(ISkinObj)兩個方面提供用戶擴展。
繪圖對象(ISkinObj)的擴展
系統內置了如SSkinImgList, SSkinImgFrame, SSkinScrollbar等繪圖對象,在窗口中通過引用這些繪圖對象可以繪制出不同的預定義圖形圖象(如按鈕,滾動條,九宮格等)。
實際上用戶可以實現任意的繪圖對象並把它們注冊到系統里,以便在XML及代碼中使用。
下面先看一下實現一個ISkinObj需要實現哪些接口:
/** * @struct ISkinObj * @brief Skin 對象 * * Describe */ class SOUI_EXP ISkinObj : public SObject,public TObjRefImpl2<IObjRef,ISkinObj> { public: ISkinObj() { } virtual ~ISkinObj() { } /** * Draw * @brief 將this繪制到RenderTarget上去 * @param IRenderTarget * pRT -- 繪制用的RenderTarget * @param LPCRECT rcDraw -- 繪制位置 * @param DWORD dwState -- 繪制狀態 * @param BYTE byAlpha -- 透明度 * @return void * Describe */ virtual void Draw(IRenderTarget *pRT, LPCRECT rcDraw, DWORD dwState,BYTE byAlpha=0xFF)=0; /** * GetSkinSize * @brief 獲得Skin的默認大小 * @return SIZE -- Skin的默認大小 * Describe 派生類應該根據skin的特點實現該接口 */ virtual SIZE GetSkinSize() { SIZE ret = {0, 0}; return ret; } /** * IgnoreState * @brief 查詢skin是否有狀態信息 * @return BOOL -- true有狀態信息 * Describe */ virtual BOOL IgnoreState() { return TRUE; } /** * GetStates * @brief 獲得skin對象包含的狀態數量 * @return int -- 狀態數量 * Describe 默認為1 */ virtual int GetStates() { return 1; } };
ISkinObj是一個派生自SObject及TObjRefImpl2<IObjRef,ISkinObj>的類,提供了幾個狀態查詢相關的接口,也提供了一個Draw接口來在IRenderTarget上繪制該繪制對象。
注意的是,這些接口中只有Draw接口是純虛接口。
在它的基類中,SOjbect使得ISkinObj可以方便的從XML配置文件中初始化,而TObjRefImpl2<IObjRef,ISkinObj>則提供引用計數的實現。
內置的ISkinObj不支持顯示GIF圖片,以顯示GIF圖象為例來分析如何擴展繪圖對象來支持GIF圖片顯示。
對象定義(trunk\controls.extend\gif\SSkinGif.h)
namespace SOUI { class SGifFrame { public: CAutoRefPtr<IBitmap> pBmp; int nDelay; }; /** * @class SSkinGif * @brief GIF圖片加載及顯示對象 * * Describe */ class SSkinGif : public ISkinObj { SOUI_CLASS_NAME(SSkinGif, L"gif") public: SSkinGif():m_nFrames(0),m_iFrame(0),m_pFrames(NULL) { } //初始化GDI+環境,由於這里需要使用GDI+來解碼GIF文件格式 static BOOL Gdiplus_Startup(); //退出GDI+環境 static void Gdiplus_Shutdown(); virtual ~SSkinGif() { if(m_pFrames) delete [] m_pFrames; } /** * Draw * @brief 繪制指定幀的GIF圖 * @param IRenderTarget * pRT -- 繪制目標 * @param LPCRECT rcDraw -- 繪制范圍 * @param DWORD dwState -- 繪制狀態,這里被解釋為幀號 * @param BYTE byAlpha -- 透明度 * @return void * Describe */ virtual void Draw(IRenderTarget *pRT, LPCRECT rcDraw, DWORD dwState,BYTE byAlpha=0xFF); /** * GetStates * @brief 獲得GIF幀數 * @return int -- 幀數 * Describe */ virtual int GetStates(){return m_nFrames;} /** * GetSkinSize * @brief 獲得圖片大小 * @return SIZE -- 圖片大小 * Describe */ virtual SIZE GetSkinSize() { SIZE sz={0}; if(m_nFrames>0 && m_pFrames) { sz=m_pFrames[0].pBmp->Size(); } return sz; } /** * GetFrameDelay * @brief 獲得指定幀的顯示時間 * @param int iFrame -- 幀號,為-1時代表獲得當前幀的延時 * @return long -- 延時時間(*10ms) * Describe */ long GetFrameDelay(int iFrame=-1); /** * ActiveNextFrame * @brief 激活下一幀 * @return void * Describe */ void ActiveNextFrame(); /** * SelectActiveFrame * @brief 激活指定幀 * @param int iFrame -- 幀號 * @return void * Describe */ void SelectActiveFrame(int iFrame); /** * LoadFromFile * @brief 從文件加載GIF * @param LPCTSTR pszFileName -- 文件名 * @return int -- GIF幀數,0-失敗 * Describe */ int LoadFromFile(LPCTSTR pszFileName); /** * LoadFromMemory * @brief 從內存加載GIF * @param LPVOID pBits -- 內存地址 * @param size_t szData -- 內存數據長度 * @return int -- GIF幀數,0-失敗 * Describe */ int LoadFromMemory(LPVOID pBits,size_t szData); SOUI_ATTRS_BEGIN() ATTR_CUSTOM(L"src",OnAttrSrc) //XML文件中指定的圖片資源名,(type:name) SOUI_ATTRS_END() protected: LRESULT OnAttrSrc(const SStringW &strValue,BOOL bLoading); int LoadFromGdipImage(Gdiplus::Bitmap * pImg); int m_nFrames; int m_iFrame; SGifFrame * m_pFrames; }; }//end of name space SOUI
對象注冊:
theApp->RegisterSkinFactory(TplSkinFactory<SSkinGif>());//注冊SkinGif
對象的使用(trunk\demo\uires\xml\dlg_main.xml):
<skin> <!--局部skin對象--> <gif name="gif_horse" src="gif:gif_horse"/> <gif name="gif_penguin" src="gif:gif_penguin"/> </skin>
這里的gif標簽與SSkinGif類中的宏SOUI_CLASS_NAME(SSkinGif,L"gif")中的“gif”是匹配的。
到此,在布局XML及程序中都可以獲得這個SSkinGif對象的指針了。
控件的擴展
控件的擴展和繪圖對象的擴展套路類似,也是先從系統提供的基礎類派生,再注冊到系統,最后再XML或者代碼中使用。
和繪圖對象不同在於,控件是UI,需要處理各種UI相關的消息以及向程序發出各種控件特有的事件。
同樣我們還是以GIF顯示的控件為例(trunk\controls.extend\gif\SGifPlayer.h):
namespace SOUI { /** * @class SGifPlayer * @brief GIF圖片顯示控件 * * Describe */ class SGifPlayer : public SWindow { SOUI_CLASS_NAME(SGifPlayer, L"gifplayer") //定義GIF控件在XM加的標簽 public: SGifPlayer(); ~SGifPlayer(); /** * PlayGifFile * @brief 在控件中播放一個GIF圖片文件 * @param LPCTSTR pszFileName -- 文件名 * @return BOOL -- true:成功 * Describe */ BOOL PlayGifFile(LPCTSTR pszFileName); protected://SWindow的虛函數 virtual CSize GetDesiredSize(LPRECT pRcContainer); public://屬性處理 SOUI_ATTRS_BEGIN() ATTR_CUSTOM(L"skin", OnAttrGif) //為控件提供一個skin屬性,用來接收SSkinObj對象的name SOUI_ATTRS_END() protected: HRESULT OnAttrGif(const SStringW & strValue, BOOL bLoading); protected://消息處理,SOUI控件的消息處理和WTL,MFC很相似,采用相似的映射表,相同或者相似的消息映射宏 /** * OnPaint * @brief 窗口繪制消息響應函數 * @param IRenderTarget * pRT -- 繪制目標 * @return void * Describe 注意這里的參數是IRenderTarget *,而不是WTL中使用的HDC,同時消息映射宏也變為MSG_WM_PAINT_EX */ void OnPaint(IRenderTarget *pRT); /** * OnTimer * @brief SOUI窗口的定時器處理函數 * @param char cTimerID -- 定時器ID,范圍從0-127。 * @return void * Describe SOUI控件的定時器是Host窗口定時器ID的分解,以方便所有的控件都通過Host獲得定時器的分發。 * 注意使用MSG_WM_TIMER_EX來映射該消息。定時器使用SWindow::SetTimer及SWindow::KillTimer來創建及釋放。 * 如果該定時器ID范圍不能滿足要求,可以使用SWindow::SetTimer2來創建。 */ void OnTimer(char cTimerID); /** * OnShowWindow * @brief 處理窗口顯示消息 * @param BOOL bShow -- true:顯示 * @param UINT nStatus -- 顯示原因 * @return void * Describe 參考MSDN的WM_SHOWWINDOW消息 */ void OnShowWindow(BOOL bShow, UINT nStatus); //SOUI控件消息映射表 SOUI_MSG_MAP_BEGIN() MSG_WM_TIMER_EX(OnTimer) //定時器消息 MSG_WM_PAINT_EX(OnPaint) //窗口繪制消息 MSG_WM_SHOWWINDOW(OnShowWindow)//窗口顯示狀態消息 SOUI_MSG_MAP_END() private: SSkinGif *m_pgif; int m_iCurFrame; }; }
實現了該類以后,在WinMain中注冊:
theApp->RegisterWndFactory(TplSWindowFactory<SGifPlayer>());//注冊GIFPlayer
我們可以在布局XML中創建GIF控件控件並顯示了(trunk\demo\uires\xml\dlg_main.xml):
<gifplayer pos="10,10" skin="gif_horse" name="giftest" cursor="ANI_ARROW"/> <button width="250" height="30" name="btnSelectGif">load gif file</button> <gifplayer pos="10,150" skin="gif_penguin"/>
上面的gifplayer節點即表示從XML中創建兩個SGifPlayer類對象。
而gifplayer對象的skin屬性則引用前面定義的SSkinGif對象。
備注:
實際上更多擴展技巧可以參考系統內置的控件的實現。內置控件與擴展控件唯一的區別就在於由誰實現將控件向系統注冊。
內置控件在SOUI內核初始化的時候自動注冊,而擴展控件則需要手動增加一行注冊代碼。
效果預覽

