Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MFC Controls using XML-Files as DataSource (XERCES-Parser)

0.00/5 (No votes)
9 Mar 2005 1  
An article on reading xml files with the xerces parser and binding the data to custom MFC Controls

Sample Image - XMLControls.jpg

Introduction

This article shows a short way to dynamically fill a list or combobox with xml-data.

Background

I didn't wanted to write an article about what you read now at this very moment. I wanted to play around with the xerces parser to learn its interface. After the first look I decided to write a simple base class for reading data from xml files easily with xerces. The result is the class CXMLBase. And know that you are reading my first article for The Code Project. I've used the resources here exhaustingly. This is my first try to give back my experience. If there are any more questions feel free to contact me!

Used techniques

  • XML (xerces-parser)
  • STL (maps and list) a very easy, fast and efficient way to store/manage your stuff in memory
  • MFC (owner drawing) fastest possibility to get ur xml data drawn from the "std-datastore"

Using the code

class CXMLBase is the base class for all different kinds of controls or services. class CXMLGrid is derived from class CXMLBase. My idea behind this inheritance was to get one more baseclass for list like controls. i.e. comboboxes, lists, grids, maybee trees, but I think this could be done more easy in an extra derivation from class CXMLBase like class CXMLTree

1. Step (Creating the xml file!)

My datasource has a grid like look and feel, so you have to divide it into rows and cols. The body tag is <XMLGRID>. You have to assign an id in order to get the bound to the right control. label and icons are extracted to their according member variables, but the MFC controls do not use it. I'm going to implement this features later. <header> will define the look an feel of the single columns.

First col (index 0) will be used to get the specific icon name. I'm going to use the grid in virtual mode, so u can get an unlimited amount of rows and an other rowsource (i.e. recordset from a db query). Got anyone a good idea how to implement this better? Second col (index 1) shows the according string which will be show in the control. Third col is a user defined value. In derived controls you are able to define as much cols as you want in order to get all values in your app. All data is written to a stl::map of CXMLGridData m_rows.

<?xml version="1.0" encoding="ISO-8859-1"?>
<XMLGRID id="maps" label="Map-Typen" icons="../test/data">
    <header>
        <col id="1">label</col>
        <col id="2">filter</col>
        <col id="0">icon</col>
    </header>
    <row >
        <col id="1">Defuse (DE)</col>
        <col id="2">de_</col>
        <col id="0">de.ico</col>
    </row>
    <row >    
        <col id="1">Rescue (CS)</col>
        <col id="2">cs_</col>
        <col id="0">cs.ico</col>
    </row>
    <row >    
        <col id="1">Aim (AIM)</col>
        <col id="2">aim</col>
        <col id="0">aim.ico</col>
    </row>
    <row >    
        <col id="1">Defuse and Rescue (CSDE)</col>
        <col id="2">csde_</col>
        <col id="0">csde.ico</col>
    </row>
</XMLGRID>

2. Step (Creating the member)

Use the class wizard to create a member i.e. m_cboXMLList of type CXMLListBox Don't forget to include the header files!! XMLListBox.h or XMLComboBox.h Now you are ready to initialize the control and use it. Simply call Init() from the OnInitDialog() (or where ever you want it to be initialized)

    m_cboXMLGrid.Init( "data.xml", "maps");
    m_lstXMLGrid.Init( "data.xml", "maps");

3. Step (Have fun!!)

That's all. Data will be shown in the control. Code definition for the base classes (included in the sources!!) Data maps used for storing the xml stuff in rows and cols

// =================

typedef std::map                CColsMap;
typedef std::pair               CColsPair;
typedef std::map            CXMLGridData;
typedef std::pair           CXMLGridDataPair;

// ================= CXMLGridRow

class CXMLGridRow
{
public:
    long        m_lIndex;
    CColsMap    *m_pCols;

    CXMLGridRow()
    {
        m_pCols = new CColsMap();
        m_lIndex = -1;
    };

    CXMLGridRow(const CXMLGridRow &src)
    {
        m_lIndex = src.m_lIndex;
        m_pCols = new CColsMap( *(src.m_pCols));
    };

    ~CXMLGridRow()
    {
        if( m_pCols )
        {
            m_pCols->clear();
            delete m_pCols;
        }
        m_pCols = NULL;
    };

    void Insert( const char *lpcszEntry)
    {
        (*m_pCols)[m_pCols->size()] = _T(lpcszEntry);
    };

    void Insert( int iIdx, const char *lpcszEntry)
    {
        (*m_pCols)[iIdx] = _T(lpcszEntry);
    };
};

The base class for all of our weird xml data. Parse sets up the xerces and gives you the document pointer.

// ================= CXMLBase

class CXMLBase
{
public:
    bool Init(const char* lpcszXMLFile, const char* lpcszID);

protected:
    virtual void Parse( DOMDocument *doc, const char* lpcszID)=0;
};

This is the funky class for all list like controls. Parse() calls ParseRow() if a <row> tag is found, parses its Attributes and its Header -> ParseRow() looks up for cols. So if you want to write your own service or control simply derive a class of class CXMLGrid and define the functions needed to add new features. Don't forget to call its adequate virtual to provide the base functions. Only in case of redefining the tags used for base classes define Parse(). Otherwise Parse() will call OnUnknownAttribute() to provide user implementations.

WARNING: Redefining could lead to an unknown behavior of my provided code. So please check carefully. I suggest to use other tags instead of redefining them or first simply call the base class Parse() function and then do your own parsing. This will lead to an overhead but ensures full functionality of the base classes.

// ================= CXMLGrid

class CXMLGrid : public CXMLBase
{
public:
    CXMLGrid(){ m_lRowCount = 0; };
    ~CXMLGrid(){};

    virtual void Parse           ( DOMDocument    *doc, const char* lpcszID    );
    virtual void ParseRow        ( DOMNode    *node                            );
    virtual void ParseCol        ( DOMNode    *node, CXMLGridRow &row          );
    virtual void ParseAttributes ( DOMNode    *node                            );
    virtual void ParseHeader     ( DOMNode    *node                            );
    virtual void OnUnknownAttribute( DOMNode    *node                          );

    CXMLGridData    m_rows;

protected:
    CXMLGridRow        m_header;
    long               m_lRowCount;
    CString            m_strLabel;
    CString            m_strID;
    CString            m_strIcons;
};

Conclusion

Playing around with xerces is weird stuff and fun fun fun. It is a very powerful tool. Getting started with it is no problem. Sample code is included in the distribution. The documentation is great. If you have a basic understanding of reading a manual, you'll get started very fast. My biggest problem was to prepare the code for parsing the file itself. I have used the copy-paste wizard (included in every good IDE ;-) ) to Parse() the document. All other kind of parsing code is just like playing around! Good job by the devteam from xerces. Number 1!!! And the best, you know, all free :-)

Points of Interest

If anyone got a good idea for features, please contact me!!

History

  • 2005-03-10 1.000 Initial release
  • 2005-03-11 1.001 Added icon drawing, modified some code (still searching for good and small code to load and draw icons from a file (no resource!!)
  • 2005-03-11 1.002 Added OnUnknownAttribute() to provide user defined tags without to rewrite the parse function. Added some more documentation to the article.

Thanks to

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here