Click here to Skip to main content
15,881,172 members
Articles / Desktop Programming / MFC
Article

Loading dynamic XML from the Internet

Rate me:
Please Sign up or sign in to vote.
3.63/5 (5 votes)
20 Jun 20028 min read 94.2K   1.4K   34   14
A simple example of loading XML data from an ASP page on an IIS Server.

Introduction

I was recently assigned a project that required a program to display a menu of choices based on a user's current account status and type. This required the program to contact our server and check the user's information and send back appropriate data. The program downloads XML data to build the menu choices. This is rather straight forward when the data is constant since you can use IXMLDOMDocument2::load to open an XML file from any URL. However, I needed to pass the user's ID to the server and allow it to build the data on the fly. This does not work with the load function as it only retrieves a static file.

The only way to load dynamic XML data is by using the IXMLDOMDocument2::loadXML function to load the data from a string. In this case, the string is created by using WinInet functions to open a connection and load an ASP page on the server, which returns proper XML formatted data. This is done using InternetOpenURL. The code in this sample handles the data transfer synchronously since the data set is small.

The Sample Project

The sample I have created is a step-by-step example. If you follow these instructions, you will have written your own copy of the program which you can execute. (This is my favorite way to learn new programming topics.)

The program we are going to create is a simple Win32 application with one window. The client area of the window is divided into four quadrants. When the mouse is right-clicked in the window, a popup menu will be displayed. When you right-click, the number of the quadrant you clicked is sent to the server. The server then determines which menu items are to be displayed. Selecting an item in the popup menu will simply open a website in your web browser.

Step One: Creating the Workspace

Open Visual C++ and use the New Project Wizard. Choose Win32 Application, type a project name and click OK. I chose to name my project webxml. On step 1 of 1, choose A typical "Hello World!" application. This will provide more boilerplate code than we really need, but it gets us to a good starting point.

Step Two: COM Support

We are going to be using COM components in the program, so we need to initialize COM. To do this, first open stdafx.h and add an include for <atlbase.h>. Doing this will eliminate the need for <windows.h>.

// Windows Header Files:
//#include <windows.h> //comment or remove this line
#include <atlbase.h>   // and add this line

Next, we load COM when the program starts and unload COM right before the program ends. Open webxml.cpp and add the following code to WinMain.

// TODO: Place code here.
 MSG msg;
 HACCEL hAccelTable;

 CoInitialize(NULL);  // add this line

 //
 // there is wizard generated code here
 //

 CoUninitialize();        // add this line
 return msg.wParam;

Step 3: A little bit of drawing

We are going to draw two lines to demark the quadrants for the different popup menus. While not really necessary for the example, it will help a bit. Here is the entire WM_PAINT code from WndProc after it has been updated with drawing code:

case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    // TODO: Add any drawing code here...
    RECT rt;
    GetClientRect(hWnd, &rt);
 //DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
    MoveToEx(hdc, rt.right / 2, rt.top, NULL);
    LineTo(hdc, rt.right / 2, rt.bottom);
    MoveToEx(hdc, rt.left, rt.bottom / 2, NULL);
    LineTo(hdc, rt.right, rt.bottom / 2);
    EndPaint(hWnd, &ps);
    break;

Step 4: Adding Extra Support Files and Settings

We are going to be using XML, WININET API, Shell API and a couple of STL classes. First we are going to link to wininet.lib. Go to the Project menu and select Settings.... Click on the Link tab. In the Object/library modules textbox, add wininet.lib to the list. (Remember to do this for both Debug and Release versions.)

Next we open stdafx.h and add the following lines:

// TODO: reference additional headers your program requires here
#include <string>
#include <vector>
#include <wininet.h>
#include <shellapi.h>
#import "msxml3.dll"

Optionally, you can remove the following #include statements as they are not needed.

// C RunTime Header Files
//#include <stdlib.h>
//#include <malloc.h>
//#include <memory.h>
//#include <tchar.h>

Step 5: Build the Project

We need to build the project at this point. If you are unfamiliar with the #import directive, you should know that this directive tells the build process to create files needed to access the COM objects in the file you are #importing. These files are needed for code we will be adding in the rest of the article.

Step 6: Our Data

Now that we have all the preparation done, we can start on the main objective: read XML from the Internet and create our popup menu.

If our data was static, we could simply create an XML file on the server and use MSXML features to simply open the file, parse it and display our menu. In fact, this is how the first quadrant works. It simply loads menu.xml from the server. Here is the XML file:

XML
<?xml version="1.0"?>
<list>
    <menuitem label="Static Menu" url="" enabled="0" />
    <menuitem label="" url="" enabled="1" />
    <menuitem label="the Code Project" 
       url="http://www.codeproject.com/" enabled="1" />
    <menuitem label="MSDN" 
       url="http://msdn.microsoft.com/" enabled="1" />
    <menuitem label="Darwin Awards" 
       url="http://www.darwinawards.com/" enabled="1" />
</list>

This list contains menu items. Each menu item has a label (the text displayed on the menu), a URL (the website that will be loaded when the item is selected, and an enabled flag (whether the menu item is enabled)).

Disabled menu items do not need a URL since you will not be able to click on them anyway. Any menu item that has a blank label will be displayed in the menu as a separator.

For our purposes, this simply will not do. We want to choose what the user sees at runtime based on input. So here is the ASP page that we will use.

VBScript
<%@ Language=VBScript %>
<list>
    <menuitem label="Dynamic Menu" url="" enabled="0" />
    <menuitem label="" url="" enabled="1" />
    <menuitem label="the Code Project" 
       url="http://www.codeproject.com/" enabled="1" />
<%
    Select Case (Request("quad"))
        Case 2:
%>
            <menuitem label="Quad #2" url="" enabled="0" />
            <menuitem label="" url="" enabled="1" />
            <menuitem label="Bored" 
               url="http://www.bored.com/" enabled="1" />
            <menuitem label="The Zone" 
               url="http://zone.msn.com/" enabled="1" />
            <menuitem label="Jumped The Shark" 
               url="http://www.jumpedtheshark.com/" enabled="1" />
<%
        Case 3:
%>
            <menuitem label="Quad #3" url="" enabled="0" />
            <menuitem label="" url="" enabled="1" />
            <menuitem label="Fox News" 
               url="http://www.foxnews.com/" enabled="1" />
            <menuitem label="MSNBC" 
               url="http://www.msnbc.com/" enabled="1" />
            <menuitem label="CNN" 
               url="http://www.cnn.com/" enabled="1" />
<%
        Case 4:
%>
            <menuitem label="Quad #4" url="" enabled="0" />
            <menuitem label="" url="" enabled="1" />
            <menuitem label="Hockey" 
               url="http://www.NHL.com/" enabled="1" />
            <menuitem label="USA Football" 
               url="http://www.NFL.com/" enabled="1" />
            <menuitem label="Intl. Football" 
               url="http://www.soccernet.com/" enabled="1" />
<%
    End Select
%>
</list>

If you are unfamiliar with ASP, don't worry. You can use any type of server-side programming you want as long as the data you send from the server is in XML format without the <?xml version="1.0"?> line. This page simply checks GET or POST data for quad and branches based on that value.

Step 7: Catching the mouse

When the user right-clicks a quadrant, we will call a routine to build and display the popup menu. So, we add code to the WndProc:

case WM_RBUTTONDOWN:
    POINT pt;
    pt.x = LOWORD(lParam);
    pt.y = HIWORD(lParam);
    ShowMenu(hWnd, pt);
    break;

Step 8: Forward Declarations

These are the functions we will be adding. The forward declarations are placed at the top of webxml.cpp. You can place all of these now because we will not successfully build the project until all of these are at least stubbed.

// handles the UI part of the menu
void ShowMenu(HWND hWnd, POINT pt); 
// handles getting XML from the web into memory
void LoadXMLData(int quad);
// assembles the menu resource from memory
void BuildMenu(HMENU hPopup);
// opens default browser for selected menu item
void LaunchPage(int index);

Step 9: Displaying the Menu

Then we need a ShowMenu function. This function will determine which menu to show, call helper functions to create the popup menu and then display the menu.

void ShowMenu(HWND hWnd, POINT pt)
{
    int quad = 0;
    RECT rt;

    GetClientRect(hWnd, &rt);
    if (pt.x < rt.right / 2)
    {
        if (pt.y < rt.bottom / 2)
            quad = 1;
        else
            quad = 3;
    }
    else
    {
        if (pt.y < rt.bottom / 2)
            quad = 2;
        else
            quad = 4;
    }

    LoadXMLData(quad);

    HMENU hPopup = CreatePopupMenu();
    BuildMenu(hPopup);
    ClientToScreen(hWnd, &pt);
    TrackPopupMenu(hPopup, 
       TPM_LEFTALIGN | TPM_TOPALIGN, 
       pt.x, pt.y, 0, hWnd, NULL);
    DestroyMenu(hPopup);
}

Step 10: A Place to Store Data

Next we need some variables to store our data. The program will populate three vectors. One vector holds the label, one holds the URL and the last holds the enabled flag (from the XML files). We will declare these variables globally at the top of webxml.cpp.

// variables
std::vector<std::string>  sLabels;
std::vector<std::string>  sUrls;
std::vector<std::string>  sFlags;

Step 11: Loading the Data

We place the LoadXMLData function in webxml.cpp. The function first creates an XML Document and sets the async property to false. This ensures that the document will be entirely loaded before one of the load functions returns. Then, if we are loading quadrant one, the XML file on the server is simply loaded. Otherwise, we have to open an Internet connection, open the ASP page and read the XML data at that URL. (I plan on creating a separate article on this process and post it here in future.) The file is read into a string which is loaded by the XML Document.

Next, the arrays we created are cleared and the XML Document is walked to read the data into the arrays. I have eliminated all error checking in this example. However, if the XML file was not loaded for any reason, a dummy menu is created.

void LoadXMLData(int quad)
{
    MSXML2::IXMLDOMDocument2Ptr 
       pDoc(__uuidof(MSXML2::DOMDocument));
    MSXML2::IXMLDOMNodeListPtr      pList = NULL;
    MSXML2::IXMLDOMNodePtr          pNode = NULL;
    BOOL    bLoaded = FALSE;

    pDoc->async = FALSE;

    if (quad == 1)
        bLoaded = pDoc->load
          (_T("http://www.tenandsix.com/CP/Articles/webxml/menu.xml"));
    else
    {
        TCHAR       sBuf[512];
        DWORD       dwBytesRead  = 0;
        std::string sXml;

        HINTERNET hInet = InternetOpen(_T("webxml Sample"), 
            INTERNET_OPEN_TYPE_PRECONFIG, 
            NULL, NULL, NULL);
        if (hInet)
        {
            std::string sUrl = 
              _T("http://www.tenandsix.com/CP/Articles/webxml/menu.asp?quad=");
            TCHAR sQuad[2];
            itoa(quad, sQuad, 10);
            sUrl.append(sQuad);

            HINTERNET hUrl = 
                InternetOpenUrl(hInet, sUrl.c_str(), NULL, -1L,
                INTERNET_FLAG_RELOAD | 
                INTERNET_FLAG_PRAGMA_NOCACHE |
                INTERNET_FLAG_NO_CACHE_WRITE, NULL);
            if (hUrl)
            {
                BOOL bRead = InternetReadFile(hUrl, &sBuf, 512, &dwBytesRead);
                while (bRead && (dwBytesRead > 0))
                {
                    sXml.append(sBuf, dwBytesRead);
                    bRead = InternetReadFile(hUrl, &sBuf, 512, &dwBytesRead);
                }
                InternetCloseHandle(hUrl);
            }
            InternetCloseHandle(hInet);
        }

        bLoaded = pDoc->loadXML(sXml.c_str());
    }

    sLabels.clear();
    sUrls.clear();
    sFlags.clear();

    if (bLoaded)
    {
        pList = pDoc->documentElement->selectNodes("//menuitem");
        for (long i = 0; i < pList->Getlength(); ++i)
        {
            pNode = pList->Getitem(i)->attributes->getNamedItem("label");
            sLabels.push_back((LPCTSTR)(_bstr_t)pNode->GetnodeValue());

            pNode = pList->Getitem(i)->attributes->getNamedItem("url");
            sUrls.push_back((LPCTSTR)(_bstr_t)pNode->GetnodeValue());

            pNode = pList->Getitem(i)->attributes->getNamedItem("enabled");
            sFlags.push_back((LPCTSTR)(_bstr_t)pNode->GetnodeValue());
        }
    }
    else
    {
        sLabels.push_back(_T("No Menu"));
        sUrls.push_back(_T(""));
        sFlags.push_back(_T("0"));
    }
}

Step 12: Building the Menu

Next we create the menu. BuildMenu is given the handle of a popup menu. The vectors are read and the menu items are created and appended to this popup.

One thing to note is the wID parameter of the MENUITEMINFO structure. I have coded it to be 500 + i. This will be how we know which menu item was clicked in our WM_COMMAND handler. Since i is the index into the vector for the label, it will correspond to the URL in the sUrls vector.

void BuildMenu(HMENU hPopup)
{
    MENUITEMINFO mii;
    ZeroMemory(&mii, sizeof(MENUITEMINFO));
    mii.cbSize = sizeof(MENUITEMINFO);

    for (int i = 0; i < sLabels.size(); ++i)
    {
        if (std::string(sLabels[i]).length() == 0)
        {
            mii.fMask = MIIM_TYPE;
            mii.fType = MFT_SEPARATOR;
            InsertMenuItem(hPopup, GetMenuItemCount(hPopup), TRUE, &mii);
        }
        else
        {
            mii.fMask = MIIM_STRING | MIIM_ID | MIIM_STATE;
            TCHAR* pTemp = new TCHAR[std::string(sLabels[i]).size()+1];
            ZeroMemory(pTemp, std::string(sLabels[i]).size()+1);
            CopyMemory(pTemp, 
              std::string(sLabels[i]).c_str(), 
              std::string(sLabels[i]).size()+1);
            if (std::string(sFlags[i]) == _T("1"))
                mii.fState = MFS_ENABLED;
            else
                mii.fState = MFS_DISABLED;
            mii.cch = std::string(sLabels[i]).size();
            mii.dwTypeData = pTemp;
            mii.wID = 500 + i;
            InsertMenuItem(hPopup, 
              GetMenuItemCount(hPopup), TRUE, &mii);
            delete [] pTemp;
        }
    }
}

Step 13: Handling the Menu Commands

We will modify the WM_COMMAND portion of WndProc to call a function to load the webpage associated with the selected menu item. Again, the index of the selected menu item is 500 + i, so we need to reverse it. Perhaps this step is not needed, but I have done this to give us a known range of values that will not duplicate any other resources' symbols.

case WM_COMMAND:
    wmId    = LOWORD(wParam);
    wmEvent = HIWORD(wParam);

    // Parse the menu selections:
    if (wmId >= 500 && wmId <= sLabels.size() + 500)
        LaunchPage(wmId - 500);

    switch (wmId)
    {
        case IDM_ABOUT:
            DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break;

Step 14: Launching the URL

LaunchPage is a simple function that takes an index, looks-up the URL and uses ShellExecute to load it in the default browser.

void LaunchPage(int index)
{
    ShellExecute(NULL, "open", std::string(sUrls[index]).c_str(), 
        NULL, NULL, SW_SHOWNORMAL);
}

Step 15: Build and Execute the program

Hopefully the program compiles correctly. If it does not, please leave a comment indicating the problem (no doubt there's a typo in there somewhere). When you run the program, right-click in each of the quadrants. If you see a menu that says "No Menu", then we have a problem. Otherwise, you should see a different menu in each quadrant. Select a menu item to load the selected site in your default browser.

Afterword

I have tried to create this article to demonstrate the development process I went through to accomplish a similar task. Hopefully, following along this article helped give a starting point for using the functions in it (like the XML COM objects and InternetXxx functions). I decided to keep detailed explanation to a minimum to better convey the way they can be used. However, I feel that good articles explaining how best to use the XML COM objects and WinInet API functions would be helpful. If you feel there is a need (or if they already exist and I have just not found them yet), let me know.

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


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAsyncronus Document loading Pin
The DevMan3-Aug-04 2:59
The DevMan3-Aug-04 2:59 
QuestionHow to Copy/Move Temporary Internet Files? Pin
Stuard20-Sep-02 9:32
Stuard20-Sep-02 9:32 
AnswerRe: No Pin
Tim Smith20-Sep-02 9:41
Tim Smith20-Sep-02 9:41 
AnswerRe: How to Copy/Move Temporary Internet Files? Pin
Zero_divide_114-Sep-03 19:16
Zero_divide_114-Sep-03 19:16 
GeneralMulti level menu(i.e menu and submenus) Pin
Anonymous17-Jul-02 7:56
Anonymous17-Jul-02 7:56 
QuestionTwo MENUITEMINFO structures? Pin
21-Jun-02 10:11
suss21-Jun-02 10:11 
AnswerRe: Two MENUITEMINFO structures? Pin
21-Jun-02 10:29
suss21-Jun-02 10:29 
GeneralRe: Two MENUITEMINFO structures? Pin
Tim Smith21-Jun-02 10:42
Tim Smith21-Jun-02 10:42 
GeneralRe Pin
21-Jun-02 11:09
suss21-Jun-02 11:09 
GeneralRe: Two MENUITEMINFO structures? Pin
Tom Welch21-Jun-02 16:34
Tom Welch21-Jun-02 16:34 
GeneralHooking into IE Context Pin
TigerNinja_21-Jun-02 7:05
TigerNinja_21-Jun-02 7:05 
GeneralRe: Hooking into IE Context Pin
Tom Welch21-Jun-02 8:01
Tom Welch21-Jun-02 8:01 
Generaldemo project link is not valid Pin
20-Jun-02 19:48
suss20-Jun-02 19:48 
GeneralRe: demo project link is not valid Pin
Tom Welch21-Jun-02 2:22
Tom Welch21-Jun-02 2:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.