第二十二篇:在SOUI中使用代碼向窗口中插入子窗口


使用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                 }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中創建它的子窗口,主要注意代碼中紅色部分。

其中第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 SWindow
2 {
3 SOUI_CLASS_NAME(myctrl,L"myctrl")
4 public:
5 //...
6 virtual void OnFinalRelease() {delete this;}
7 //...
8 };

感謝大家的支持!

 


免責聲明!

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



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