第二十一篇:SOUI中的控件注冊機制


Win32編程中,用戶需要一個新控件時,需要向系統注冊一個新的控件類型。注冊以后,調用::CreateWindow時才能根據標識控件類型的字符串創建出一個新的控件窗口對象。

為了能夠從XML描述的字符串中創建出需要的控件對象,和Win32類似,在SOUI中要創建一個新的控件也同樣需要向SOUI系統注冊新的控件類。

從demo.cpp的main中我們可以看到類似如下的控件注冊控件的代碼:

        //向SApplication系統中注冊由外部擴展的控件及SkinObj類
        SWkeLoader wkeLoader;
        if(wkeLoader.Init(_T("wke.dll")))        
        {
            theApp->RegisterWndFactory(TplSWindowFactory<SWkeWebkit>());//注冊WKE瀏覽器
        }
        theApp->RegisterWndFactory(TplSWindowFactory<SGifPlayer>());//注冊GIFPlayer
        theApp->RegisterSkinFactory(TplSkinFactory<SSkinGif>());//注冊SkinGif
        theApp->RegisterSkinFactory(TplSkinFactory<SSkinAPNG>());//注冊SSkinAPNG
        theApp->RegisterSkinFactory(TplSkinFactory<SSkinVScrollbar>());//注冊縱向滾動條皮膚

        theApp->RegisterWndFactory(TplSWindowFactory<SIPAddressCtrl>());//注冊IP控件
        theApp->RegisterWndFactory(TplSWindowFactory<SPropertyGrid>());//注冊屬性表控件
        theApp->RegisterWndFactory(TplSWindowFactory<SChromeTabCtrl>());//注冊ChromeTabCtrl
        theApp->RegisterWndFactory(TplSWindowFactory<SIECtrl>());//注冊IECtrl
        theApp->RegisterWndFactory(TplSWindowFactory<SChatEdit>());//注冊ChatEdit
        theApp->RegisterWndFactory(TplSWindowFactory<SScrollText>());//注冊SScrollText
        
        if(SUCCEEDED(CUiAnimation::Init()))
        {
            theApp->RegisterWndFactory(TplSWindowFactory<SUiAnimationWnd>());//注冊動畫控件
        }
        theApp->RegisterWndFactory(TplSWindowFactory<SFlyWnd>());//注冊飛行動畫控件
        theApp->RegisterWndFactory(TplSWindowFactory<SFadeFrame>());//注冊漸顯隱動畫控件
        theApp->RegisterWndFactory(TplSWindowFactory<SRadioBox2>());//注冊漸顯隱動畫控件

上面代碼中即有新皮膚對象的注冊也有窗口控件的注冊,這種注冊控件方式看起來有點怪異,但使用起來也還算是簡單,只需要一行代碼(實際上是從著名的游戲GUI CEGUI借鑒過來的)。

以注冊窗口控件為例,下面我來解釋一下為什么會有這樣一種控件注冊方式。

先看看TplSWindowFactory模板類的實現:

    class SWindowFactory
    {
    public:
        virtual ~SWindowFactory() {}
        virtual SWindow* NewWindow() = 0;
        virtual LPCWSTR SWindowBaseName()=0;

        virtual const SStringW & getWindowType()=0;

        virtual SWindowFactory* Clone() const =0;
    };

    template <typename T>
    class TplSWindowFactory : public SWindowFactory
    {
    public:
        //! Default constructor.
        TplSWindowFactory():m_strTypeName(T::GetClassName())
        {
        }

        LPCWSTR SWindowBaseName(){return T::BaseClassName();}

        // Implement WindowFactory interface
        virtual SWindow* NewWindow()
        {
            return new T;
        }

        virtual const SStringW & getWindowType()
        {
            return m_strTypeName;
        }

        virtual SWindowFactory* Clone() const 
        {
            return new TplSWindowFactory();
        }
    protected:
        SStringW m_strTypeName;
    };
TplSWindowFactory從SWindowFactory派生,自動為模板參數中指定的類型生成一個SWindowFactory對象。
SWindowFactory有什么用呢?SWindowFactory最核心的用途就在於它的方法:SWindow * SWindowFactory::NewWindow();
該方法說來很簡單,不過是根據不同的字符串從堆上分配一個SWindow對象,但是在C++中我們需要滿足一條基本原則:內存在哪個模塊中分配(從哪個模塊的堆上分配)就必須被哪個模塊釋放。
在SOUI中,通常情況下控件都是由SOUI.DLL這個模塊來分配內存的,也就是說SWindow * SWindowFactory::NewWindow()只會在SOUI模塊中調用。

那么生成的對象在哪里釋放呢?
這就需要看一下SWindow或者SSkinObjBase這兩個類的實現了。
    class SOUI_EXP SWindow : public SObject
        , public SMsgHandleState
        , public TObjRefImpl2<IObjRef,SWindow>
    {
        SOUI_CLASS_NAME(SWindow, L"window")
        //....
    };

    class SOUI_EXP SSkinObjBase : public TObjRefImpl<ISkinObj>
    {
       //......
    };
SWindow和SSkinObjBase實際上都派生自TObjRefImpl這個模板類。下面看一下這個類的實現:
template<class T>
class TObjRefImpl :  public T
{
public:
    TObjRefImpl():m_cRef(1)
    {
    }

    virtual ~TObjRefImpl(){
    }

    //!添加引用
    /*!
    */
    virtual void AddRef()
    {
        InterlockedIncrement(&m_cRef);
    }

    //!釋放引用
    /*!
    */
    virtual void Release()
    {
        InterlockedDecrement(&m_cRef);
        if(m_cRef==0)
        {
            OnFinalRelease();
        }
    }

    //!釋放對象
    /*!
    */
    virtual void OnFinalRelease()
    {
        delete this;
    }
protected:
    volatile LONG m_cRef;
};

template<class T,class T2>
class TObjRefImpl2 :  public TObjRefImpl<T>
{
public:
    virtual void OnFinalRelease()
    {
        delete static_cast<T2*>(this);
    }
};
TObjRefImpl一個重要的虛函數是void OnFinalRelease(){delete this;}

由於SWindow及SSkinObjBase是在SOUI模塊中實現的,因此派生自這兩個類的新的控件類及皮膚類最后都將在SOUI模塊中被釋放,從而保證了對象內存的分配、釋放在一個模塊。
這也就是說不管是SOUI模塊內實現的控件還是在應用層擴展的控件一般情況下都應該向SOUI系統注冊並經由SOUI內核來實現對象的分配與釋放。

那么問題來了,是不是所有新控件都必須向系統注冊呢?
當然不是的,注意OnFinalRelease是一個虛函數,只需要在新的控件類中增加一個
void OnFinalRelease(){delete this;}
就可以把控件內存的釋放轉移到應用層的模塊了。如此你就可以在自己的模塊中直接使用new來創建新控件了。(可以參考屬性表控件中部分子控件的實現)

 


免責聲明!

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



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