通过XML创建界面---对象的动态创建以及属性的设置
为了界面可配置化和换肤,需要界面元素可以根据XML动态创建和设置属性。
在BKLib中,CBkObject类就完成了这样的功能,主要负责类的创建和属性的设置。因为对象都是从XML动态创建的,动态的创建是一个类最基本的属性,所以其他类都从CBkObject派生。
来看看这个类的四个方法:
BOOL IsClass(LPCSTR lpszName):判断是不是这个类的对象。纯虚方法,也就是从CBkObject继承来的类都要实现这个方法才行,同时,这个方法在不同的类上面会有不同的表现。所以上层定义接口,下层提供实现。这个方法可以在运行时检测类实例的实际类型。
LPCSTR GetObjectClass():同上一个方法,用于获取类的名字。
BOOL Load(TiXmlElement* pXmlElem):从XML中获取属性并将属性设置到对象中,在基类中仅仅是将一个XML元素的属性设置到对象中。当然,如果子类对象有更复杂的实现,比如一个对象对应的XML元素还有子节点,就需要Load其子节点,这些都可以在子类中通过覆盖父类方法来实现。
HRESULT SetAttribute(CStringAstrAttribName, CStringA strValue, BOOL bLoading):设置属性的方法,CBkObject是纯虚类,在XML中不会有对应的节点,自然也没有相应的属性,所以其实现仅仅放回了一个E_FAIL,没有其他操作。
接着我们就看到了在bkobject.h里面一大堆的宏定义:
宏定义一般是为了简洁,而这些宏的用途多为子类使用。
BKOBJ_DECLARE_CLASS_NAME:获取类名,判断是否是某个类的对象,还有CheckAndNew,用于动态创建
以下的宏定义主要用于属性的设置和映射(XML节点属性和对象属性的对应)
BKWIN_DECLARE_ATTRIBUTES_BEGIN
BKWIN_DECLARE_ATTRIBUTES_END
BKWIN_CHAIN_ATTRIBUTE
BKWIN_CUSTOM_ATTRIBUTE
BKWIN_INT_ATTRIBUTE
BKWIN_UINT_ATTRIBUTE
BKWIN_DWORD_ATTRIBUTE
BKWIN_STRING_ATTRIBUTE
BKWIN_TSTRING_ATTRIBUTE
BKWIN_HEX_ATTRIBUTE
BKWIN_COLOR_ATTRIBUTE
BKWIN_FONT_ATTRIBUTE
BKWIN_ENUM_ATTRIBUTE
BKWIN_ENUM_VALUE
BKWIN_ENUM_END
BKWIN_STYLE_ATTRIBUTE
BKWIN_SKIN_ATTRIBUTE
现在我们看一个例子,继承自CBkObject的控件对象CBkProgress是如何完成从XML动态创建的。
首先,在类的定义中包含宏BKOBJ_DECLARE_CLASS_NAME(CBkProgress,"progress"),将宏展开如下:
public:
static CBkProgress* CheckAndNew(LPCSTR lpszName)
{
if (strcmp(GetClassName(), lpszName) == 0)
return new CBkProgress;
else
return NULL;
} //通过传入名称创建对应的类,在解析XML中按照节点名字创建对应类的实例
static LPCSTR GetClassName()
{
return “progress”;
}
virtual LPCSTR GetObjectClass()
{
return “progress”;
} //覆盖父类方法,返回类实例对应的XML节点名字
virtual BOOL IsClass(LPCSTR lpszName)
{
return strcmp(GetClassName(), lpszName) == 0;
} //覆盖父类方法,根据XML节点名字检查类实例是否是此节点
另外,包含设置节点属性的宏,如下
BKWIN_DECLARE_ATTRIBUTES_BEGIN()
BKWIN_SKIN_ATTRIBUTE("bgskin",m_pSkinBg,TRUE)
BKWIN_DWORD_ATTRIBUTE("min",m_dwMinValue,FALSE)
BKWIN_UINT_ATTRIBUTE("showpercent",m_bShowPercent,FALSE)
BKWIN_DECLARE_ATTRIBUTES_END()
将宏展开如下:
public:
virtual HRESULT SetAttribute(
CStringA strAttribName,
CStringA strValue,
BOOL bLoading) //添加SetAttribute方法,在Load中循环调用设置属性
{
HRESULT hRet = __super::SetAttribute( //首先设置父类定义的属性
strAttribName,
strValue,
bLoading
);
if (SUCCEEDED(hRet))
return hRet;
if ("bgskin" == strAttribName) //属性名称是strAttribName
{
m_pSkinBg = BkSkin::GetSkin(strValue); //属性的值是strValue
hRet = TRUE ? S_OK : S_FALSE; //是否全部重绘
}
else
if ("min" == strAttribName)
{
m_dwMinValue = (DWORD)::StrToIntA(strValue);
hRet = FALSE ? S_OK : S_FALSE;
}
else
if ("showpercent" == strAttribName)
{
m_bShowPercent = (UINT)::StrToIntA(strValue);
hRet = FALSE ? S_OK : S_FALSE;
}
else
return E_FAIL;
return hRet;
}
现在我们来看看CheckAndNew和SetAttribute这两个方法是如何被调用的,看调用栈:
1.在实窗口的Create方法中(DoModal中调用Create)调用实窗口的Load和SetXml方法装载XML,在SetXML方法中查找XML中存在的"header"、"body"、"footer"节点调用各自的Load方法并设置相应属性。这三个节点开始就是虚窗口了,调用其Load方法就进入了虚窗口的创建体系。
2.在这三者的调用中会调用CBkPanel::Load方法
virtual BOOLLoad(TiXmlElement*pTiXmlElem)
{
if (!CBkWindow::Load(pTiXmlElem)) //调用父类load方法,主要进行自身属性设置
returnFALSE;
return LoadChilds(pTiXmlElem->FirstChildElement()); //load子节点
}
在CBkPanel::LoadChilds方法中,顺序调用每个子节点的创建方法并调用Load方法。
3. 在CBkPanel::LoadChilds方法中进行了如下调用:
CBkWindow*pNewChildWindow =_CreateBkWindowByName(pXmlChild->Value());
在_CreateBkWindowByName函数根据从XML解析出的节点名称调用
pNewWindow= CBkDialog::CheckAndNew(lpszName); // CBkDialog为需要动态创建类的名称
创建出对应节点对象。安装我们展开的CheckAndNew方法,如果节点名称相同,创建类对象并返回,否则返回空。
至此,按照XML节点名称动态创建类对象的过程就完成了。