使用SOUI開發客戶端UI程序,一般也推薦使用XML代碼來建立窗口,這樣建立的窗口使用方便,當窗口大小改變時,內部的子窗口也更容易協同變化。node
可是最近不斷有網友諮詢如何使用代碼來建立SOUI子窗口,特此在這裏統一解答。函數
要回答這個問題,首先要了解SOUI窗口建立及佈局的流程。佈局
先從swnd.cpp裏抄一段建立子窗口的代碼:ui
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 }else 18 { 19 LOADXML(xmlDoc,strLst[0],RT_LAYOUT); 20 } 21 if(xmlDoc) 22 { 23 CreateChildren(xmlDoc.child(KLabelInclude)); 24 }else 25 { 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中建立它的子窗口,主要注意代碼中紅色部分。this
其中第30行是從SOUI的窗口類廠根據XML結點名new出一個窗口對象。spa
第33行把建立出來的窗口插入到this的子窗口鏈表裏去。code
而第34行的功能是從子窗口對應的XML結點初始化子窗口屬性。xml
不少網友覺得只要完成上面步驟就應該能夠顯示窗口了,但結果確大失所望。對象
SOUI一個重要特色就是可以自動佈局,這個過程的祕密就在於SWindow::OnRelayout方法。blog
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 SWindow 2 { 3 SOUI_CLASS_NAME(myctrl,L"myctrl") 4 public: 5 //... 6 virtual void OnFinalRelease() {delete this;} 7 //... 8 };
感謝你們的支持!