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>.
#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
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:
hdc = BeginPaint(hWnd, &ps);
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);
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:
Optionally, you can remove the following
#include statements as they are not needed.
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:
<menuitem label="Static Menu" url="" enabled="0" />
<menuitem label="" url="" enabled="1" />
<menuitem label="the Code Project"
url="http://www.codeproject.com/" enabled="1" />
url="http://msdn.microsoft.com/" enabled="1" />
<menuitem label="Darwin Awards"
url="http://www.darwinawards.com/" enabled="1" />
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.
<%@ Language="VBScript" %>
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
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
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.
void ShowMenu(HWND hWnd, POINT pt);
void LoadXMLData(int quad);
void BuildMenu(HMENU hPopup);
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;
if (pt.x < rt.right / 2)
if (pt.y < rt.bottom / 2)
quad = 1;
quad = 3;
if (pt.y < rt.bottom / 2)
quad = 2;
quad = 4;
HMENU hPopup = CreatePopupMenu();
TPM_LEFTALIGN | TPM_TOPALIGN,
pt.x, pt.y, 0, hWnd, NULL);
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.
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::IXMLDOMNodeListPtr pList = NULL;
MSXML2::IXMLDOMNodePtr pNode = NULL;
BOOL bLoaded = FALSE;
pDoc->async = FALSE;
if (quad == 1)
bLoaded = pDoc->load
DWORD dwBytesRead = 0;
HINTERNET hInet = InternetOpen(_T("webxml Sample"),
NULL, NULL, NULL);
std::string sUrl =
itoa(quad, sQuad, 10);
HINTERNET hUrl =
InternetOpenUrl(hInet, sUrl.c_str(), NULL, -1L,
BOOL bRead = InternetReadFile(hUrl, &sBuf, 512, &dwBytesRead);
while (bRead && (dwBytesRead > 0))
bRead = InternetReadFile(hUrl, &sBuf, 512, &dwBytesRead);
bLoaded = pDoc->loadXML(sXml.c_str());
pList = pDoc->documentElement->selectNodes("//menuitem");
for (long i = 0; i < pList->Getlength(); ++i)
pNode = pList->Getitem(i)->attributes->getNamedItem("label");
pNode = pList->Getitem(i)->attributes->getNamedItem("url");
pNode = pList->Getitem(i)->attributes->getNamedItem("enabled");
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
void BuildMenu(HMENU hPopup)
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);
mii.fMask = MIIM_STRING | MIIM_ID | MIIM_STATE;
TCHAR* pTemp = new TCHAR[std::string(sLabels[i]).size()+1];
if (std::string(sFlags[i]) == _T("1"))
mii.fState = MFS_ENABLED;
mii.fState = MFS_DISABLED;
mii.cch = std::string(sLabels[i]).size();
mii.dwTypeData = pTemp;
mii.wID = 500 + i;
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.
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
if (wmId >= 500 && wmId <= sLabels.size() + 500)
LaunchPage(wmId - 500);
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
return DefWindowProc(hWnd, message, wParam, lParam);
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.
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.