The dialog manager, part 3: Creating the controls

Date:March 31, 2005 / year-entry #82
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20050331-00/?p=36003
Comments:    14
Summary:This is actually a lot less work than creating the frame, believe it or not. For each control in the template, the corresponding child window is created. The control's sizes and position is specified in the template in DLUs, so of course they need to be converted to pixels. int x = XDLU2Pix(ItemTemplate.x); int y...

This is actually a lot less work than creating the frame, believe it or not.

For each control in the template, the corresponding child window is created. The control's sizes and position is specified in the template in DLUs, so of course they need to be converted to pixels.

  int x = XDLU2Pix(ItemTemplate.x);
  int y = YDLU2Pix(ItemTemplate.y);
  int cx = XDLU2Pix(ItemTemplate.cx);
  int cy = YDLU2Pix(ItemTemplate.cy);

The class name and caption also come from the template. There are also the optional extra bytes pExtra which nobody uses but which remain in the template definition for historical reasons. Once that information has been collected, it's time to make the donuts.

  HWND hwndChild = CreateWindowEx(
              ItemTemplate.dwExStyle | WS_EX_NOPARENTNOTIFY,
              pszClass, pwzCaption, ItemTemplate.dwStyle,
              x, y, cx, cy, hdlg, ItemTemplate.dwId,
              hinst, pExtra);

Notice that the WS_EX_NOPARENTNOTIFY style is forced on for dialog controls.

This next part often trips people up. "When I try to create my dialog, it fails and I don't know why." It's probably because one of the controls on the dialog could not be created, usually because you forgot to register the window class for that control. (For example, you forgot to call the InitCommonControlsEx function or you forgot to LoadLibrary the appropriate version of the RichEdit control.)

  if (!hwndChild) {
    DestroyWindow(hdlg);
    return NULL;
  }

The DS_NOFAILCREATE style suppresses the failure check above.

But if the control did get created, then it needs to be initialized.

  SetWindowContextHelpId(hwndChild, ItemTemplate.dwHelpID);
  SetWindowFont(hwndChild, hf, FALSE);

Repeat once for each item template, and you now have a dialog box with all its child controls. Tell the dialog procedure that it can initialize its child windows, show the (now-ready) dialog box if we deferred the WS_VISIBLE bit when constructing the frame, and return the dialog box to our caller, ready for action.

  // The default focus is the first item that is a valid tab-stop.
  HWND hwndDefaultFocus = GetNextDlgTabItem(hdlg, NULL, FALSE);
  if (SendMessage(hdlg, WM_INITDIALOG, hwndDefaultFocus, lParam)) {
     SetDialogFocus(hwndDefaultFocus);
  }

  if (fWasVisible) ShowWindow(hdlg);
  return hdlg;
}

The SetDialogFocus function we saw last year.

So there you have it: You have now seen how dialog box sausages are made.

(Actually, reality is much sausagier, since I skipped over all the app compat hacks! For example, there's a program out there that relies on the subtle placement and absence of the WS_BORDER style to decide whether a control is a combo box or a listbox. I guess the GetClassName function was too much work?)

I hope this helps you understand a little better how dialog templates fit into the big picture.


Comments (14)
  1. According to the docs, DS_NOFAILCREATE is a Win9x-only thingie. Too bad…

  2. KiwiBlue says:

    Raymond wrote:

    For example, there’s a program out there that relies on the subtle placement and absence of the WS_BORDER style to decide whether a control is a combo box or a listbox.

    Is this program trying to implement resizable dialogs?

  3. Adrian says:

    I’m thrilled to get all these dialog manager details in this series. I spent a long time figuring out how it all works for various custom things I’ve had to do in the past. I’ll be curious to see if I’ve missed anything as Raymond illuminates this mysterious and misuderstood part of Windows.

    Here’s a painful lesson I’ve learned about dialog units:

    When converting dialog units to pixels, there’s a subtle difference between converting the corners of the rectangle (as MapDialogRect does) and converting the top-left corner and the dimensions (as Raymond has shown). In the former approach, the exact size can depend on where the control is placed on the dialog. The way Raymond has shown it, the sizes will always be consistent, regardless of where on the dialog the control is placed. It both schemes, it can be impossible to get consistent spacing between controls in a long list (like a stack of radio buttons or checkboxes).

    As your dimensions get larger, the truncated division remainders become significant.

    I wish UI designers better understood these limitations, so they’d realize that we simply can’t place items to the pixel.

  4. Brian says:

    I’m curious, in the code do you mark the appcompat stuff really clearly?

    Something like:

    HWND hwndDefaultFocus = GetNextDlgTabItem(hdlg, NULL, FALSE);

    // BEGIN APP COMPAT — WidgetCo’s Widget Extreme 2000 assumes this is null

    if ( app == WIDGETEXTREME2000 ) {

    hwndDefaultFocus = NULL;

    }

    // END APP COMPAT

  5. Jon Potter says:

    A long time ago I designed a dialog manager for the Amiga which used two sets of coordinates for every object. One was a "character coordinate" that scaled with the font, and the other was a "pixel coordinate" that was absolute. It made it a lot more complicated designing dialogs (especially since I never bothered to write a graphical dialog editor for it) but it did let you position things with pixel accuracy if you wanted.

  6. John Elliott says:

    That Amiga dialog manager sounds like the way GEM stores object trees in its resource files; objects have a position in characters and an optional offset in pixels. The resource editor distinguishes between ‘dialogs’ (everything snaps to a character grid) and ‘panels’ (pixel offsets are allowed) but this is just an editing convenience; they both end up as a TREE resource. So, for that matter, do menus.

  7. could you expand a little on why Windows enforces the WS_EX_NOPARENTNOTIFY flag.

  8. Raymond Chen says:

    WS_EX_NOPARENTNOTIFY: I think you can guess.

  9. because the parent is the dialog who just created the controls? doh!

  10. Norman Diamond says:

    (For example, you forgot to call the

    > InitCommonControlsEx function

    Now that was good for a chuckle. Case sensitivity slipped it under the covers, but reading the linked page revealed it:

    > BOOL InitCommonControlsEx(

    > LPINITCOMMONCONTROLSEX lpInitCtrls

    > );

    A short pointer is inadequate to init common control sex, right?

  11. gkdada says:

    May be this is the wrong place, but this is not really all THAT off-topic, considering you are discussing dialogs.

    Why do dialog controls refuse to use the provided Rgn and insist on using the rectangle instead? Recently I ran into solid problem with these and had to invent a CNonButton class.

  12. gkdada – Refuse to use the provided Rgn for… erm… what, precisely?

    If you’re talking about painting buttons, and wondering why they don’t draw their frames in the same shape as their regions, it’s because it would look butt ugly in most cases, and you as a developer should provide your own special-case code.

    Besides, XP Themes would cause problems with this.

  13. asp fan says:

    Please explain the reason for WS_EX_NOPARENTNOTIFY. It’s useful for mouse events and not only for creation/destruction of the child window.

  14. gkdada says:

    Simon,

    Here are the steps:

    1. I do a SetWindowRgn to set a round-rect rgn (for example) to a button.

    2. In OnPaint(of the subclassed button class), I do a GetWindowRgn and FillRgn with a color or pattern or something.

    3. I then call FrameRgn to paint a border onto the button (using the same Rgn that GetWindowRgn returned in step 2)

    In the case of a CButton derived class, this results in step 2 filling a RECTANGULAR region, while step 3 framing a ROUND-RECT region. In other words, the paint spills outside the framed area.

    If you derive your button class directly from CWnd (and do all the button things painstakingly) with the exact same steps mentioned above, the button comes out nice and clean. No spills.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index