第十篇:擴展SOUI的控件及繪圖對象(ISkinObj)


盡管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內核初始化的時候自動注冊,而擴展控件則需要手動增加一行注冊代碼。

效果預覽

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM