SOUI官方论坛

 找回密码
 立即注册
查看: 220|回复: 0

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

[复制链接]
  • TA的每日心情
    开心
    昨天 08:14
  • 签到天数: 2 天

    [LV.1]初来乍到

    357

    主题

    792

    帖子

    6871

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    6871
    发表于 2019-12-1 10:10:21 | 显示全部楼层 |阅读模式
    使用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.             if(_wcsicmp(xmlChild.name(),KLabelInclude)==0)
    8.             {//在窗口布局中支持include标签
    9.                 SStringW strSrc = S_CW2T(xmlChild.attribute(L"src").value());
    10.                 pugi::xml_document xmlDoc;
    11.                 SStringTList strLst;

    12.                 if(2 == ParseResID(strSrc,strLst))
    13.                 {
    14.                     LOADXML(xmlDoc,strLst[1],strLst[0]);
    15.                 }else
    16.                 {
    17.                     LOADXML(xmlDoc,strLst[0],RT_LAYOUT);
    18.                 }
    19.                 if(xmlDoc)
    20.                 {
    21.                     CreateChildren(xmlDoc.child(KLabelInclude));
    22.                 }else
    23.                 {
    24.                     SASSERT(FALSE);
    25.                 }
    26.             }else if(!xmlChild.get_userdata())//通过userdata来标记一个节点是否可以忽略
    27.             {
    28.                 SWindow *pChild = SApplication::getSingleton().CreateWindowByName(xmlChild.name());
    29.                 if(pChild)
    30.                 {
    31.                     InsertChild(pChild);
    32.                     pChild->InitFromXml(xmlChild);
    33.                 }
    34.             }
    35.         }
    36.         return TRUE;
    37.     }
    复制代码
    这个函数的功能是为this从XML中创建它的子窗口,主要注意代码中红色部分。
    其中第30行是从SOUI的窗口类厂根据XML结点名new出一个窗口对象。
    第33行把创建出来的窗口插入到this的子窗口链表里去。
    而第34行的功能是从子窗口对应的XML结点初始化子窗口属性。
    很多网友以为只要完成上面步骤就应该可以显示窗口了,但结果确大失所望。
    SOUI一个重要特点就是能够自动布局,这个过程的秘密就在于SWindow::On***out方法。
    1. void SWindow::On***out(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.         SSendMessage(WM_NCCALCSIZE);//计算非客户区大小

    13.         UpdateChildrenPosition();   //更新子窗口位置

    14.         CRect rcClient;
    15.         GetClientRect(&rcClient);
    16.         SSendMessage(WM_SIZE,0,MAKELPARAM(rcClient.Width(),rcClient.Height()));
    17.     }
    复制代码
    当this窗口位置改变后都会进入On***out方法。
    注意函数第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.     virtual ~TObjRefImpl(){
    9.     }

    10.     //!添加引用
    11.     /*!
    12.     */
    13.     virtual long AddRef()
    14.     {
    15.         return InterlockedIncrement(&m_cRef);
    16.     }

    17.     //!释放引用
    18.     /*!
    19.     */
    20.     virtual long Release()
    21.     {
    22.         long lRet = InterlockedDecrement(&m_cRef);
    23.         if(lRet==0)
    24.         {
    25.             OnFinalRelease();
    26.         }
    27.         return lRet;
    28.     }

    29.     //!释放对象
    30.     /*!
    31.     */
    32.     virtual void OnFinalRelease()
    33.     {
    34.         delete this;
    35.     }
    36. protected:
    37.     volatile LONG m_cRef;
    38. };

    39. template<class T,class T2>
    40. class TObjRefImpl2 :  public TObjRefImpl<T>
    41. {
    42. public:
    43.     virtual void OnFinalRelease()
    44.     {
    45.         delete static_cast<T2*>(this);
    46.     }
    47. };
    复制代码
    注意代码中的OnFinalRelease,它是一个虚方法。因此对于使用new创建的窗口对象,只需要在窗口类中抄一段代码如下即可:
    class myctrl : public SWindow
    {
    SOUI_CLASS_NAME(myctrl,L"myctrl")
    public:
    //...
    virtual void OnFinalRelease() {delete this;}
    //...

    };
    感谢大家的支持!

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|SOUI官方论坛 ( 粤ICP备18103663号-2 )

    GMT+8, 2020-1-18 08:18

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表