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

使用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 };

感谢你们的支持!

相关文章
相关标签/搜索