Enumerating Controls of a Dialog Resource at Runtime

What you use it for is up to you, of course. The function by itself doesn't do anything, it only parses through the dialog resource. At various points in the code you'll see exactly where you can grab info and make use of it. I needed to roll my own property sheet and property page classes for use on a CDialogBar, and decided the code might be useful to others. This code was compiled under VC5 Service Pack 3, but I assume VC6 will run it as well.

In the code you'll notice sections like this:


wPointer++;
WORD wMenuOrd = (*wPointer);
wPointer++;
This is where you come in and do stuff with the value retreived. The data structures are DWORD-aligned and always variable in length, we have to be careful to account for everything. IE don't yank sections of unused code unless you're accounting for the byte length elsewhere.

The dialog resources use NULL-terminated Unicode strings... two bytes per character. I've put in boiler plate code to load them up as CStrings using the LPCWSTR cast. Obviously if you don't need a particular string, you can yank that assignment. Or conversely, if you like working in wide-character strings, you can change it to use _bstr_t or whatever flavor you prefer.

Uses for this function

  • Property sheet on a CDialogBar. Using the resource ID for every control on a dialog resource allowed me to separate the dialog bar template from the pages that would be layered on it. What a pain it is to deal with layers and layers of controls. CPropertySheet is great, but it's not a toolbar. I have one main dialog bar template during development with nothing but the Tab control on it. Every page has its own template with matching width/height. These templates are really only placeholders for the controls. When it comes time to build, I bulk copy all of the page controls to the dialog bar by manually editing myapp.rc. Then in a custom page class, I parse through the page templates one at a time, assigning the resource IDs for those controls to a specific page number. When the user switches tabs, I hide/show the respective controls by making calls to GetDlgItem(). There's more to it than that quick explanation, but you get the idea.
  • Dynamically resizable CFormView derivatives. Knowing where every control is located on a dialog template relative to the overall width/height of that dialog would allow you to calculate their new positions during resizing. You wouldn't have to add new code every time you placed a new control on the page, or worry about changing code if you alter the resource name.

Okay, on with the code!


BOOL EnumerateDialogResource(UINT iResourceID)
{
 // Dialog base unit -> pixel unit conversion macros
 #define DLGUNIT_TO_PIXEL_X(dlgX)	\
  ( (dlgX * LOWORD( ::GetDialogBaseUnits() )) / 4)

 #define DLGUNIT_TO_PIXEL_Y(dlgY)	\ 
  ( (dlgY * HIWORD( ::GetDialogBaseUnits() )) / 8)

 // DWORD-alignment macro
 #define DWORD_ALIGN(x)  x = ((x + 3) & ~3)
	
 LPVOID lpResource = NULL;
 HGLOBAL hgResource = NULL;	
 HINSTANCE hInst = AfxGetResourceHandle();

 // Find resource handle
 HRSRC hrDlg = ::FindResource(hInst, 
  MAKEINTRESOURCE(iResourceID), RT_DIALOG);

 if (hrDlg == NULL)
 {
  return FALSE;
 }
	
 // Load it
 hgResource = LoadResource(hInst, hrDlg);
 if (hgResource == NULL)
 {
  return FALSE;
 }

 // Lock it
 lpResource = LockResource(hgResource);
 if (lpResource == NULL)
 {
  return FALSE;		
 }
		
 // Use these to enumerate the resource controls
 LPDLGTEMPLATE lpTemplate;
 LPDLGITEMTEMPLATE lpItemTemplate;	
	
 // Use these to keep track of where we're at
 DWORD dwDlgTemplateSize = sizeof (DLGTEMPLATE);		
 DWORD dwDlgItemTemplateSize = sizeof (DLGITEMTEMPLATE);		
 DWORD dwDataPosition = 0;

 // This guy will move through the global data
 WORD *wPointer = NULL;

 // Cast a BYTE pointer to the global data 
 // so we can manipulate it
 BYTE *pbyGlobalData = (BYTE *)(lpResource);	
	
 // This is the dialog itself
 lpTemplate = (LPDLGTEMPLATE)pbyGlobalData;
 dwDataPosition = dwDlgTemplateSize;

 // The dialog's menu is the first 
 // item after the dialog template (1.)	
 wPointer = (WORD *)(pbyGlobalData + dwDataPosition);
	
 if ((*wPointer) == 0x0000)
 {
  // No menu resource
  wPointer++;
 }
 else
 {
  if ((*wPointer) == 0xffff)
  {			
   // There's one other element which specifies
   // the ordinal value of the menu
   wPointer++;
   WORD wMenuOrd = (*wPointer);
   wPointer++;
  }
  else
  {
   // It's a string specifying the 
   // name of a menu resource
   CString strMenuResource ((LPCWSTR)wPointer);			
   while ((*wPointer++) != 0x0000);
  }		
 }

 // Next comes the window class
 if ((*wPointer) == 0x0000)
 {
  // The system uses the predefined dialog 
  // box class for the dialog box
  wPointer++;
 }
 else
 {
  if ((*wPointer) == 0xffff)
  {
   // One other element which specifies the 
   // ordinal value of a predefined system window class
   wPointer++;
   WORD wClassOrd = (*wPointer);
   wPointer++;
  }
  else
  {
   // It's a string specifying the name of a registered window class
   CString strWndClass ((LPCWSTR)wPointer);
   while ((*wPointer++) != 0x0000);
  }
 }

 // Now comes the title of the dialog box
 if ((*wPointer) == 0x0000)
 {
  // There is no title
  wPointer++;
 }
 else
 {		
  CString strDlgTitle ((LPCWSTR)wPointer);
  while ((*wPointer++) != 0x0000);
 }

 // Check for the font point size and 
 // typeface members.  These may or may not be there.	
 if (lpTemplate->style & DS_SETFONT)
 {
  WORD wPointSize = (*wPointer);
  wPointer++;
		
  // The font name
  CString strTypeFace ((LPCWSTR)wPointer);
  while ((*wPointer++) != 0x0000);
 }

 // Now that we've passed all of the DLGTEMPLATE 
 // stuff, we need to make sure we're aligned on a DWORD boundary
 DWORD_ALIGN (dwDataPosition);

 // These fields are not in pixel values; need to 
 // map the values properly. See the DLGTEMPLATE definition
 // for more info on these and the other fields
 UINT iDlgX = DLGUNIT_TO_PIXEL_X(lpTemplate->x);
 UINT iDlgY = DLGUNIT_TO_PIXEL_Y(lpTemplate->y);
 UINT iDlgWidth = DLGUNIT_TO_PIXEL_X(lpTemplate->cx);
 UINT iDlgHeight = DLGUNIT_TO_PIXEL_Y(lpTemplate->cy);

 // Use this to keep track of each DLGITEMTEMPLATE size
 DWORD dwItemSize = 0;

 // How many controls are we dealing with? 
 UINT iNumControls = lpTemplate->cdit;
	
 // Spin through all of the controls
 for (int j = 0; j < iNumControls; j++)
 {
  // First point to the item's template
  lpItemTemplate = (LPDLGITEMTEMPLATE)(pbyGlobalData + dwDataPosition);

  // Fields for DLGITEMTEMPLATE:
  // DWORD style
  // DWORD dwExtendedStyle
  // short x
  // short y
  // short cx
  // short cy
  // WORD id

  // Like the DLGTEMPLATE, you'll need to map the 
  // dialog base units for X and Y to 
  // pixel values... DLGUNIT_TO_PIXEL_X and DLGUNIT_TO_PIXEL_Y

  // Point to the arrays after the DLGITEMTEMPLATE (2.)
  wPointer = (WORD *)(pbyGlobalData 
   + dwDataPosition + dwDlgItemTemplateSize);
		
  // Check out the window class of this control
  if ((*wPointer) == 0xffff)
  {
   // It's a predefined system class (button, edit box, static, 
   // list box, scroll bar, or combo box.)
   wPointer++;
   WORD wClassOrd = (*wPointer);
   wPointer++;
  }
  else
  {
   // It's the name of a registered windows class
   CString strTypeFace ((LPCWSTR)wPointer);
   while ((*wPointer++) != 0x0000);
  }
		
  // Check out the title array
  if (*wPointer == 0xffff)
  {
   // This is the ordinal value of a resource 
   // in an executable, such as an icon
   wPointer++;
   WORD wResourceOrd = (*wPointer);
   wPointer++;
  }
  else
  {
   // This is the initial text for the control
   CString strInitialText ((LPCWSTR)wPointer);
   while ((*wPointer++) != 0x0000);
  }
		
  // Check out the creation array.  Use this to 
  // determine how large the creation array is.  The creation 
  // array is passed as a pointer in the lParam parameter of the 
  // WM_CREATE message.
  WORD wCADataSize = 0;

  if (*wPointer == 0x0000)
  {
   // There is no creation array
   wCADataSize = sizeof(WORD);
  }
  else
  {
   // The first item represents the size of the array (which includes
   // the first item, too!)
   wCADataSize = (*wPointer);

   // The creation array is really a CREATESTRUCT
   CREATESTRUCT *lpCreateStruct = (CREATESTRUCT *)wPointer;
  }

  // Figure out how big the item is; it needs to be DWORD-aligned
  dwItemSize = (((BYTE *)wPointer) 
   - ((BYTE *)lpItemTemplate)) + wCADataSize;

  DWORD_ALIGN (dwItemSize);
		
  // That's it... the entire DLGITEMTEMPLATE has been accounted for
  dwDataPosition += dwItemSize;
 }	
	
 // Cleanup
 if (lpResource != NULL && hgResource != NULL)
 {
  UnlockResource(hgResource);
  FreeResource(hgResource);
 }
 return TRUE;
}

Notes:

  • Blatant rip from MSDN:

    In a standard template for a dialog box, the DLGTEMPLATE structure is always immediately followed by three variable-length arrays that specify the menu, class, and title for the dialog box. When the DS_SETFONT style is given, these arrays are also followed by a 16-bit value specifying point size and another variable-length array specifying a typeface name. Each array consists of one or more 16-bit elements. The menu, class, title, and font arrays must be aligned on WORD boundaries.

  • In a standard template for a dialog box, the DLGITEMTEMPLATE structure is always immediately followed by three variable-length arrays specifying the class, title, and creation data for the control. Each array consists of one or more 16-bit elements.

    Each DLGITEMTEMPLATE structure in the template must be aligned on a DWORD boundary. The class and title arrays must be aligned on WORD boundaries. The creation data array must be aligned on a WORD boundary.