標籤:
使用SOUI開發用戶端UI程式,通常也推薦使用XML代碼來建立視窗,這樣建立的視窗使用方便,當視窗大小改變時,內部的子視窗也更容易協同變化。
但是最近不斷有網友諮詢如何使用代碼來建立SOUI子視窗,特此在這裡統一解答。
要回答這個問題,首先要瞭解SOUI視窗建立及布局的流程。
先從swnd.cpp裡抄一段建立子視窗的代碼:
1 BOOL SWindow::CreateChildren(pugi::xml_node xmlNode) 2 { 3 TestMainThread(); 4 for (pugi::xml_node xmlChild=xmlNode.first_child(); xmlChild; xmlChild=xmlChild.next_sibling()) 5 { 6 if(xmlChild.type() != pugi::node_element) continue; 7 8 if(_wcsicmp(xmlChild.name(),KLabelInclude)==0) 9 {//在視窗布局中支援include標籤10 SStringW strSrc = S_CW2T(xmlChild.attribute(L"src").value());11 pugi::xml_document xmlDoc;12 SStringTList strLst;13 14 if(2 == ParseResID(strSrc,strLst))15 {16 LOADXML(xmlDoc,strLst[1],strLst[0]);17 }else18 {19 LOADXML(xmlDoc,strLst[0],RT_LAYOUT);20 }21 if(xmlDoc)22 {23 CreateChildren(xmlDoc.child(KLabelInclude));24 }else25 {26 SASSERT(FALSE);27 }28 }else if(!xmlChild.get_userdata())//通過userdata來標記一個節點是否可以忽略29 {30 SWindow *pChild = SApplication::getSingleton().CreateWindowByName(xmlChild.name());31 if(pChild)32 {33 InsertChild(pChild);34 pChild->InitFromXml(xmlChild);35 }36 }37 }38 return TRUE;39 }
這個函數的功能是為this從XML中建立它的子視窗,主要注意代碼中紅色部分。
其中第30行是從SOUI的視窗類別廠根據XML結點名new出一個視窗對象。
第33行把建立出來的視窗插入到this的子視窗鏈表裡去。
而第34行的功能是從子視窗對應的XML結點初始化子視窗屬性。
很多網友以為只要完成上面步驟就應該可以顯示視窗了,但結果確大失所望。
SOUI一個重要特點就是能夠自動布局,這個過程的秘密就在於SWindow::OnRelayout方法。
1 void SWindow::OnRelayout(const CRect &rcOld, const CRect & rcNew) 2 { 3 SWindow *pParent= GetParent(); 4 if(pParent) 5 { 6 pParent->InvalidateRect(rcOld); 7 pParent->InvalidateRect(rcNew); 8 }else 9 {10 InvalidateRect(m_rcWindow);11 }12 13 SSendMessage(WM_NCCALCSIZE);//計算非客戶區大小14 15 UpdateChildrenPosition(); //更新子視窗位置16 17 CRect rcClient;18 GetClientRect(&rcClient);19 SSendMessage(WM_SIZE,0,MAKELPARAM(rcClient.Width(),rcClient.Height()));20 }
當this視窗位置改變後都會進入OnRelayout方法。
注意函數第15行:UpdateChildrenPosition();這個調用的功能就是將this的所有子控制項按照控制項中定義的布局屬性來自動布局。
要實現視窗的布局,除了調用父視窗的UpdateChildrenPosition()方法外,當然也可以使用SWindow::Move方法直接修改視窗位置。
看到這裡大家應該已經明白是什麼原因了。
當然上述方法是SOUI中使用的視窗建立及布局方法,具體到應用程式中使用代碼建立控制項還有一個地方需要注意。
關鍵的問題是在SOUI系統中編譯預設使用MT(靜態連結)方式來連結CRT。
MT方式編譯時間使用CRT分配記憶體是記憶體是屬性調用的模組(DLL)的,記憶體的釋放也因此必須在該模組內執行。
如果使用者直接使用new來分配一個視窗對象,並把它插入到SOUI視窗鏈表中,在視窗釋放的時機是在SWindow::OnFinalRelease()中(實際是基類TObjRefImpl2<IObjRef,SWindow>的方法)。
SWindow的程式碼片段是編譯在soui.dll中,因此預設執行記憶體釋放的代碼是在soui.dll中,從而導致程式崩潰。
要解決這個問題有兩種方法:
對於系統控制項,使用者應該使用SApplication::getSingleton().CreateWindowByName(xmlChild.name());來建立視窗對象。
而對於使用者自己派生實現的擴充視窗類別並沒有向SOUI的視窗類別廠註冊時,只能使用new方法來建立視窗對象。注意SWindow的基類:TObjRefImpl2<IObjRef,SWindow>
1 template<class T> 2 class TObjRefImpl : public T 3 { 4 public: 5 TObjRefImpl():m_cRef(1) 6 { 7 } 8 9 virtual ~TObjRefImpl(){10 }11 12 //!添加引用13 /*!14 */15 virtual long AddRef()16 {17 return InterlockedIncrement(&m_cRef);18 }19 20 //!釋放引用21 /*!22 */23 virtual long Release()24 {25 long lRet = InterlockedDecrement(&m_cRef);26 if(lRet==0)27 {28 OnFinalRelease();29 }30 return lRet;31 }32 33 //!釋放對象34 /*!35 */36 virtual void OnFinalRelease()37 {38 delete this;39 }40 protected:41 volatile LONG m_cRef;42 };43 44 template<class T,class T2>45 class TObjRefImpl2 : public TObjRefImpl<T>46 {47 public:48 virtual void OnFinalRelease()49 {50 delete static_cast<T2*>(this);51 }52 };
注意代碼中的OnFinalRelease,它是一個虛方法。因此對於使用new建立的視窗對象,只需要在視窗類別中抄一段代碼如下即可:
1 class myctrl : public SWindow2 {3 SOUI_CLASS_NAME(myctrl,L"myctrl")4 public:5 //...6 virtual void OnFinalRelease() {delete this;}7 //...8 };
感謝大家的支援!
第二十二篇:在SOUI中使用代碼向視窗中插入子視窗