VC中CTreeCtrl和xml文件的互导

程序中我们经常要用到树型控件,而持久化与展示一棵树的
方式,使用xml文件是比较恰当的。本文就此提供两个函数,
一个是根据xml文件生成树,另一个相反,将树的内容保存至
xml文件中。其实坊间也有不少例子,但大多比较繁复,抑或
所用语言有异。本文使用VC6.0 sp5写成,解析xml用MSXML4.0;
代码比较简,也没有涉及过多的譬如节点数据类型方面的东西,
以除去繁冗,突出思路。

const char ROOT[] = "//root";// 假设root为xml根节点
//------------------------------------------------------
// 1:将xml文档加载到树型控件中
//------------------------------------------------------
BOOL LoadXmlToTree(LPTSTR xmlFileName,CTreeCtrl *pTree)
{
CoInitialize(NULL);
IXMLDOMDocument2Ptr pXMLDom;
HRESULT hr= pXMLDom.CreateInstance(__uuidof(DOMDocument40));
if ( FAILED(hr) )
{
return FALSE;
}
pXMLDom->async = VARIANT_FALSE; // default - true,

// 加载xml文件
if(pXMLDom->load(_variant_t(xmlFileName)) != VARIANT_TRUE)
{
return FALSE;
}

IXMLDOMNodePtr pRootNode = pXMLDom->selectSingleNode(ROOT); // 取得根节点
if(pRootNode == NULL) return FALSE;

LPTSTR sRoot = _com_util::ConvertBSTRToString(pRootNode->GetnodeName());
HTREEITEM root = pTree->InsertItem(_T(sRoot)); // 加载根节点到树

XmlNodeToTreeNode(root,pRootNode,pTree); // 该函数在下面

pXMLDom = NULL;
pRootNode = NULL;
CoUninitialize();
return TRUE;
}

//------------------------------------------------------
// 递归将一个节点下所有子节点加入到树
//------------------------------------------------------
void XmlNodeToTreeNode(HTREEITEM parentItem, IXMLDOMNodePtr pXmlNode,CTreeCtrl *pTree)
{
if( !wcscmp(pXmlNode->GetnodeTypeString(),L"text") ) //NODE类型的含义请参考MSDN
return;

IXMLDOMNodeListPtr pXMLNodeList =  pXmlNode->GetchildNodes();
long count = pXMLNodeList->Getlength();// 子节点个数
if(count==0)
{
// 没有子节点,则设置父节点名
LPTSTR sNode = _com_util::ConvertBSTRToString(pXmlNode->GetnodeName());
pTree->SetItemText(parentItem,sNode);
return;
}
for(int i=0; i<count; i++)
{
LPTSTR sVal="";
IXMLDOMNodePtr childNode = pXMLNodeList->item[i];
HTREEITEM subItem;
if( !wcscmp(childNode->GetnodeTypeString(),L"text") )
{
// 是text的,加节点值
sVal = _com_util::ConvertBSTRToString(childNode->Gettext());
subItem = pTree->InsertItem(_T(sVal),parentItem);
}
else
{
// 不是text的,加节点名
sVal = _com_util::ConvertBSTRToString(childNode->GetnodeName());
subItem = pTree->InsertItem(_T(sVal),parentItem);

// 若是没有值的非叶子节点,就加一个空子值
if( sVal && strlen(sVal) && !childNode->hasChildNodes())
pTree->InsertItem(_T(""),subItem);
}
XmlNodeToTreeNode(subItem,childNode,pTree);// 递归子节点
}
}

----------------------------------------------------------------------------------------

//----------------------------
// 2: 将CTreeCtrl保存到xml
//----------------------------
BOOL SaveTreeToXml(CTreeCtrl *pTree, LPTSTR xmlFileName)
{
CoInitialize(NULL);
IXMLDOMDocument2Ptr pXMLDom;
HRESULT hr= pXMLDom.CreateInstance(__uuidof(DOMDocument40));
if ( FAILED(hr) )
{
return FALSE;
}
pXMLDom->async = VARIANT_FALSE; // default-true

// 加入头信息
IXMLDOMProcessingInstructionPtr pPI = NULL;
pPI = pXMLDom->createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
_variant_t vNullVal;
vNullVal.vt = VT_NULL;
pXMLDom->insertBefore(pPI, vNullVal);

HTREEITEM rootItem = pTree->GetRootItem();

// 将树的根加入Dom
IXMLDOMNodePtr pRootNode = pXMLDom->createNode(_variant_t((short)NODE_ELEMENT),
_bstr_t(pTree->GetItemText(rootItem)),
_bstr_t(""));
pXMLDom->appendChild(pRootNode);

TreeNodeToXmlNode(pTree, rootItem, pXMLDom, pRootNode); // 该函数在下面

pXMLDom->save(_variant_t(xmlFileName));
return TRUE;
}

//--------------------------------------------------------
// 递归将树中一个节点及其下所有子节点输出到xml,原理一样。
// 输入:树及根结点;xml及根结点。这里用到了DOM
// 对象来createNode,函数名超过了255,所以用extern "C"
//--------------------------------------------------------
extern "C" void TreeNodeToXmlNode(CTreeCtrl *pTree, HTREEITEM parentItem, IXMLDOMDocument2Ptr pXMLDom, 
                                                             IXMLDOMNodePtr pXmlNode)
{
if( !pTree->ItemHasChildren(parentItem) )
{
return ;
}

IXMLDOMNodePtr pChildNode;
HTREEITEM hChildItem = pTree->GetChildItem(parentItem);//得到parentItem下所有子节点
while (hChildItem != NULL)
{
CString szText = pTree->GetItemText(hChildItem);
if(!pTree->ItemHasChildren(hChildItem)) // 子为叶子
{
pXmlNode->appendChild(pXMLDom->createTextNode(_bstr_t(szText))); //puttext
}
else// 子又有子
{
pChildNode = pXMLDom->createNode(_variant_t((short)NODE_ELEMENT),
_bstr_t(_bstr_t(szText)),
_bstr_t(""));

pXmlNode->appendChild(pChildNode);
TreeNodeToXmlNode(pTree, hChildItem, pXMLDom,pChildNode); //// 递归
}
hChildItem = pTree->GetNextItem(hChildItem, TVGN_NEXT);
}

}

这里将CTreeCtrl都当作了参数,实际应用中,一般来说他们可能是某个类成员,这样就可以
不用带那么多参数了。

----------------------------------------------------------------------------------------

当在程序中构造出XML DOM时,不会有缩进,而是所有节点都在一行,
(除非一边构造Dom一边写缩进代码,但那样会使程序混乱难懂),就
像前面的SaveTreeToXml,你会发现,写出来的xml文件只有一行,所
有东西都在该行。这就需要有一个函数,将这种一行的xml转换成缩
排的格式。

extern "C" void feedtabs(IXMLDOMDocument2Ptr pDoc, IXMLDOMNodePtr pParentNode, IXMLDOMNodePtr pRefNode, int tabs)
{
CString s("/n");
if(pRefNode)//第一子之前加/n/t/t/t...(tabs个)
{
for(int i=0; i<tabs; i++) s += "/t";
pParentNode->insertBefore(pDoc->createTextNode(_bstr_t(s)),(IUnknown *)pRefNode);
}
else{//最后一子后加/n/t/t/t...((tabs-1个))
for(int i=0; i<tabs-1; i++) s += "/t";
pParentNode->appendChild( pDoc->createTextNode(_bstr_t(s)) );
}
}

extern "C" void WellFormXml(IXMLDOMDocument2Ptr pDoc, IXMLDOMNodePtr pParentNode, int tabs)
{
if(pParentNode->hasChildNodes())
{
// 第一个孩子之前加"/n/t.."(tabs个'/t')
IXMLDOMNodePtr pFirstChild = pParentNode->GetfirstChild();
feedtabs(pDoc,pParentNode,pFirstChild,tabs);

// 最后一个孩子之后加"/n/t.."(tabs-1个'/t')
feedtabs(pDoc,pParentNode,NULL,tabs);

// 递归孩子
WellFormXml(pDoc,pFirstChild,tabs+1);
}

// 递归兄弟
IXMLDOMNodePtr pNextSib = pParentNode->GetnextSibling();
if( pNextSib )
{
IXMLDOMNodePtr pGrandParent = pParentNode->GetparentNode();
if( (pGrandParent != NULL) && wcscmp(pNextSib->GetnodeTypeString(),L"text") )
{
// 在下一个兄弟之前加"/n/t.."(tabs-1个'/t')
feedtabs(pDoc,pGrandParent,pNextSib,tabs-1);
WellFormXml(pDoc,pNextSib,tabs);
}
}
}

由于不和GUI相关,所以这些函数可以做成一个Console Project来测试:

#include "afx.h"
#import <msxml4.dll>
using namespace MSXML2;

int main(void)
{
CoInitialize(NULL);
IXMLDOMDocument2Ptr pXMLDom;
HRESULT hr= pXMLDom.CreateInstance(__uuidof(DOMDocument40));
if ( FAILED(hr) )
{
printf("Fail to CreateInstance!");
return 1;
}
pXMLDom->async = VARIANT_FALSE;

if(pXMLDom->load(_variant_t("one_line.xml")) != VARIANT_TRUE) //加载你的xml
{
printf("Fail to load xml file!");
return 1;
}

IXMLDOMNodePtr rootNode = pXMLDom->selectSingleNode("//root"); //你的xml根节点
if(rootNode == NULL) return 1;

WellFormXml(pXMLDom,rootNode,1);
pXMLDom->save(_variant_t("well_formed.xml")); // 要保存的xml

pXMLDom = NULL;
rootNode = NULL;
CoUninitialize();

return 0;
}

你可能感兴趣的:(VC中CTreeCtrl和xml文件的互导)