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來創建新控件了。(可以參考屬性表控件中部分子控件的實現)