Enumerating Controls of a Dialog Resource at Runtime

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

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.



Comments

  • no need to write code by yourself

    Posted by Legacy on 03/31/2003 12:00am

    Originally posted by: Yi Zhang

    I just found out that we can use the functions provided in AFXIMPL.H, DLGTEMPL.CPP, OCCIMPL.H and OCCMGR.CPP (there are under the directory C:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC for a typical installation) to do whatever you want to DLGTEMPLATE / DLGTEMPLATEEX / DLGITEMTEMPLATE / DLGITEMTEMPLATEEX. Followed are some examples of these functions:
    1. AFX_STATIC DLGITEMTEMPLATE* AFXAPI _AfxFindFirstDlgItem(const DLGTEMPLATE* pTemplate)
    2. AFX_STATIC DLGITEMTEMPLATE* AFXAPI _AfxFindNextDlgItem(DLGITEMTEMPLATE* pItem, BOOL bDialogEx)

    Docs of CDialogTemplate:
    http://www.cppdoc.com/example/mfc/classdoc/MFC/CDialogTemplate.html#GetSizeInPixels(SIZE*)


    Hope this helps.

    • thanks

      Posted by Lissandro on 08/28/2015 06:09am

      That, Sir, helped A LOT. Thank you very much :-)

      Reply
    Reply
  • Does not work with DIALOGEX

    Posted by Legacy on 03/31/2003 12:00am

    Originally posted by: Sven Eggert

    Warning this does not function
    with DIALOGEX-resources Why?

    • DIALOGEX ...

      Posted by Lissandro on 08/28/2015 05:06am

      for DialogEx, then probably the correspondent DLGTEMPLATEEX & DLGITEMTEMPLATEEX and its related offset/data details must be used/followed as seen in https://msdn.microsoft.com/en-us/library/windows/desktop/ms645394(v=vs.85).aspx

      Reply
    Reply
  • BUG FIXED! - It won't work without it

    Posted by Legacy on 10/25/2000 12:00am

    Originally posted by: Magnus Ekvall

    Great work! I was trying to find out how to do this for half a day, before I founf this. One tiny little bug though...

    The dwDataPosition is set to the size of the DLGTEMPLATE structure, even though there is more data after it. Adding this line...

    dwDataPosition = DWORD(wPointer) - DWORD(pbyGlobalData);

    ...just before the line...

    DWORD_ALIGN (dwDataPosition);

    ...solves the problem.

    // M. Ekvall

    Reply
  • Please post code for Property sheet on a CDialogBar

    Posted by Legacy on 01/04/2000 12:00am

    Originally posted by: Peter Tran

    The enumeration of controls can be real handy, Ty. Could you post your code for implementing property sheets on a a CDialogBar? Thanks.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date