CDialogBuilder 类
说明:我这里去除了压缩文件跟资源文件只考虑_UNICODE宏
1、首先是Create(xml)文件
刚传进来的时候进行判断该字符串是否以'<'开头,如果是的话就会默认是xml文件的内容否则则默认是xml文件。所以咱们在给xml文件取名字的时候记得不能以尖括号开头。
a. 当是xml文件的时候:则会调用对应的loadfromfile->loadfrommem:判断该文件的格式是utf8(有无bom)、asci或者是unicode,然后将内容拷贝到LPTSTR m_pstrXML; 然后再调用_Parse()
b.如果是xml文件内容时直接调用_Parse()
2、调用CControlUI* CDialogBuilder::Create(IDialogBuilderCallback* pCallback, CPaintManagerUI* pManager, CControlUI* pParent)函数
先将全局的font、image、default、等添加到默认的属性列表。
再将Window的属性列表添加到默认的CPaintMangerUI类中的default 字体,颜色等
最后再调用_Parse(&root, pParent, pManager);即函数
CControlUI* CDialogBuilder::_Parse(CMarkupNode* pRoot, CControlUI* pParent, CPaintManagerUI* pManager)
3、关于_Parse三个参数的函数
先根据名字来进行解析该创建哪个控件,如果没找到则进行对应的用户自定义的类型。最后如果是在不行则调用
if( pControl == NULL && m_pCallback != NULL ) { pControl = m_pCallback->CreateControl(pstrClass); }
这里的IDialogBuilderCallback* m_pCallback;是在create的时候传进来的接口,里面有对应的createcontrol函数,我们要自己创建对应的子类来实现基类的接口。
比如duilib官方的demo
class ComputerExamineUI : public CContainerUI { public: ComputerExamineUI() { CDialogBuilder builder; CContainerUI* pComputerExamine = static_cast<CContainerUI*>(builder.Create(_T("ComputerExamine.xml"), (UINT)0)); if( pComputerExamine ) { this->Add(pComputerExamine); } else { this->RemoveAll(); return; } } }; class CDialogBuilderCallbackEx : public IDialogBuilderCallback { public: CControlUI* CreateControl(LPCTSTR pstrClass) { if( _tcscmp(pstrClass, _T("ComputerExamine")) == 0 ) return new ComputerExamineUI; return NULL; } };
对应的xml:<ComputerExamine />
完事之后
第一步:添加完成之后如果该classname有孩子则递归调用_Parse
第二步:如果有父节点则: pContainer = static_cast<IContainerUI*>(pParent->GetInterface(_T("IContainer")));到这里读者可能会疑惑为什么每个容器控件都有自身的InterfaceName那该如何进行识别?
这里以CHorizontalLayout为例
LPVOID CHorizontalLayoutUI::GetInterface(LPCTSTR pstrName) { if( _tcscmp(pstrName, DUI_CTR_HORIZONTALLAYOUT) == 0 ) return static_cast<CHorizontalLayoutUI*>(this); return CContainerUI::GetInterface(pstrName); }
看了这里应该知道了是如何获取到GetInterface的指针了,先在本身查找,如果找不到则向父节点查找。既然本身是容器那肯定会继承CContainerUI,即到了ContainerUI的时候就会正确返回了。
LPVOID CContainerUI::GetInterface(LPCTSTR pstrName) { if( _tcscmp(pstrName, _T("IContainer")) == 0 ) return static_cast<IContainerUI*>(this); else if( _tcscmp(pstrName, DUI_CTR_CONTAINER) == 0 ) return static_cast<CContainerUI*>(this); return CControlUI::GetInterface(pstrName); }
第三步:将默认的属性设置到control中,最后将用户设置的属性列表设置到control中。这样子的话如果用户重新定义了属性,则会覆盖对应的默认属性。
这里有一个pControl->SetManger(pManager, null, false); 其实这里是为了在SetAttribute属性的时候调用。
比如:
void CControlUI::SetVirtualWnd(LPCTSTR pstrValue) { m_sVirtualWnd = pstrValue; m_pManager->UsedVirtualWnd(true); }
它这里连判断pManager是否为空的都没有。所以需要调用该函数。然后在后面则进行
if( pManager ) {
pControl->SetManager(NULL, NULL, false);
}
最后说一下关于CMarkUp 跟 CMarkupNode类
首先我们知道CMarkUp是开源的一个xml解析类,然后该类有一个XMLELEMENT* m_pElements 属性,存储了每个element的相关信息,很有疑问,为什么存储的数据是ULONG iData 跟 ULONG iStart,却不是对应的字符串,这里很容易可以找到的是:
LPCTSTR CMarkupNode::GetName() const { if( m_pOwner == NULL ) return NULL; return m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iStart; } LPCTSTR CMarkupNode::GetValue() const { if( m_pOwner == NULL ) return NULL; return m_pOwner->m_pstrXML + m_pOwner->m_pElements[m_iPos].iData; }
直接返回对应的字符串!我们知道这个m_pstrXML是在刚才讲的createmem的函数中进行new的,并将文件里面的内容复制到该属性中。但是又有疑问了,那这个字符串你怎么知道有多长了,那这里就得看里面的解析函数了,在_Parse系列的函数中,当解析到了一个名字之后会在最后一个位置设置'\0'作为字符串的结束。
然后该XMLELEMENT存储的方式是以邻接表的方式进行存储的。
<Button name="cxiaoln">
datas
</Button>
这里的<Button name="cxiaoln">就是istart datas就是对应的iData 当然了,这里我只是进行简单的说明,它istart进行了'0'的分割。成各个name跟对应的值。然后对应的具体属性列表则放在CMarkupNode的_MapAttributes进行相应的解析。
CMarkupNode类进行解析对应CMarkup的属性,并将每个element的的name跟value存放在XMLATTRIBUTE m_aAttributes[MAX_XML_ATTRIBUTES];