March, 2005

  • The Old New Thing

    The dialog manager, part 3: Creating the controls

    • 14 Comments

    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.

  • The Old New Thing

    Neuroscience in the service of basketball fans

    • 3 Comments

    Nick Shulz's Transition Game pointed out Slate's experiment in using neuroscience to distract opposing foul shooters. I thought this worth mentioning now that it seems that the entire country has been swept up in NCAA basketball fever.

  • The Old New Thing

    The dialog manager, part 2: Creating the frame window

    • 16 Comments
    The dialog template describes what the dialog box should look like, so the dialog manager walks the template and follows the instructions therein. It's pretty straightforward; there isn't much room for decision-making. You just do what the template says.

    For simplicity, I'm going to assume that the dialog template is an extended dialog template. This is a superset of the classic DLGTEMPLATE, so there is no loss of generality.

    Furthermore, I will skip over some of the esoterica (like the WM_ENTERIDLE message) because that would just be distracting from the main point.

    I am also going to ignore error-checking for the same reason.

    Finally, I'm going to assume you already understand the structure of the various dialog templates and ignore the parsing issues. (If you've forgotten, you can go back and re-read my series from last June. Most important are parts 2 and 4, and the summary table is a handy quick-reference.)

    Okay, here we go.

    The first order of business is to study the dialog styles and translate the DS_* styles into WS_* and WS_EX_* styles.

    Dialog style Window style Extended window style
    DS_MODALFRAME   add WS_EX_DLGMODALFRAME
    add WS_EX_WINDOWEDGE
    DS_CONTEXTHELP   add WS_EX_CONTEXTHELP
    DS_CONTROL remove WS_CAPTION
    remove WS_SYSMENU
    add WS_EX_CONTROLPARENT

    Question: Why does the DS_CONTROL style remove the WS_CAPTION and WS_SYSMENU styles?

    Answer: To make it easier for people to convert an existing dialog into a DS_CONTROL sub-dialog by simply adding a single style flag.

    If the template includes a menu, the menu is loaded from the instance handle passed as part of the creation parameters.

      hmenu = LoadMenu(hinst, <resource identifier in template>);
    

    This is a common theme in dialog creation: The instance handle you pass to the dialog creation function is used for all resource-related activities during dialog creation.

    The algorithm for getting the dialog font goes like this:

      if (DS_SETFONT) {
        use font specified in template
      } else if (DS_FIXEDSYS) {
        use GetStockFont(SYSTEM_FIXED_FONT);
      } else {
        use GetStockFont(SYSTEM_FONT);
      }
    

    Notice that DS_SETFONT takes priority over DS_FIXEDFONT. We saw the reason for this a few weeks ago.

    Once the dialog manager has the font, it is measured so that its dimensions can be used to convert dialog units (DLUs) to pixels. Everything in dialog box layout is done in DLUs. Here's a reminder if you've forgotten the formula that converts DLUs to pixels. In explicit terms:

    // 4 xdlu = 1 average character width
    // 8 ydlu = 1 average character height
    #define XDLU2Pix(xdlu) MulDiv(xdlu, AveCharWidth, 4)
    #define YDLU2Pix(ydlu) MulDiv(ydlu, AveCharHeight, 8)
    

    The dialog box size come from the template.

    cxDlg = XDLU2Pix(DialogTemplate.cx);
    cyDlg = YDLU2Pix(DialogTemplate.cy);
    

    The dialog size in the template is the size of the client area, so we need to add in the nonclient area too.

      RECT rcAdjust = { 0, 0, cxDlg, cyDlg };
      AdjustWindowRectEx(&rcAdjust, dwStyle, hmenu != NULL, dwExStyle);
      int cxDlg = rcAdjust.right - rcAdjust.left;
      int cyDlg = rcAdjust.bottom - rcAdjust.top;
    

    How do I know that it's the client area instead of the full window including nonclient area? Because if it were the full window rectangle, then it would be impossible to design a dialog! The template designer doesn't know what nonclient metrics the end-user's system will be set to and therefore cannot take it into account at design time.

    (This is a special case of a more general rule: If you're not sure whether something is true, ask yourself, "What would the world be like if it were true?" If you find a logical consequence that is obviously wrong, then you have just proven [by contradiction] that the thing you're considering is indeed not true. This is an important logical principle that I will come back to again and again. In fact, you saw it just a few days ago. )

    Assuming the DS_ABSALIGN style is not set, the coordinates given in the dialog template are relative to the dialog's parent.

      POINT pt = { XDLU2Pix(DialogTemplate.x),
                   YDLU2Pix(DialogTemplate.y) };
      ClientToScreen(hwndParent, &pt);
    

    But what if the caller passed hwndParent = NULL? In that case, the dialog position is relative to the upper left corner of the primary monitor. But don't do this.

    • On a multiple-monitor system, it puts the dialog box on the primary monitor, even if your program is running on a secondary monitor.
    • The user may have docked their taskbar at the top or left edge of the screen, which will cover your dialog.
    • Even on a single-monitor system, your program might be running in the lower-right corner of the screen. Putting your dialog at the upper left corner doesn't create a meaningful connection between the two.
    • If two copies of your program are running, their dialog boxes will cover each other precisely. We saw the dangers of this in a previous entry.

    Moral of the story: Always pass a hwndParent window so that the dialog appears in a meaningful location relative to the rest of your program. (And don't just grab GetDesktopWindow either!)

    Okay, we are now all ready to create the dialog: We have its class, its font, its menu, its size and position...

    Oh wait, we have to deal with that subtlety of dialog box creation discussed earlier: The dialog box is always created initially hidden.

      BOOL fWasVisible = dwStyle & WS_VISIBLE;
      dwStyle &= ~WS_VISIBLE;
    

    The dialog class and title come from the template. Pretty much everyone just uses the default dialog class, although I explained in an earlier article how you might use a custom dialog class.

    Okay, now we have the information necessary to create the window.

     HWND hdlg = CreateWindowEx(dwExStyle, pszClass,
          pszCaption, dwStyle & 0xFFFF0000, pt.x, pt.y,
          cxDlg, cyDlg, hwndParent, hmenu, hinst, NULL);
    

    Notice that we filter out all the low style bits (per-class) since we already translated the DS_* styles into "real" styles.

    This is why your dialog procedure doesn't get the window creation messages like WM_CREATE. At the time the frame is created, the dialog procedure hasn't yet entered the picture. Only after the frame is created can the dialog manager attach the dialog procedure.

     // Set the dialog procedure
     SetWindowLongPtr(hdlg, DWLP_DLGPROC, (LPARAM)lpDlgProc);
    

    The dialog manager does some more fiddling at this point, based on the dialog template styles. The template may have asked for a window context help ID. And if the template did not specify window styles that permit resizing, maximizing or minimizing, the associated menu items are removed from the dialog box's system menu.

    And it sets the font.

      SetWindowFont(hdlg, hf, FALSE);
    

    This is why the first message your dialog procedure receives happens to be WM_SETFONT: It is the first message sent after the DWLP_DLGPROC has been set. Of course, this behavior can change in the future; you shouldn't rely on message ordering.

    Okay, the dialog frame is now open for business. Next up: Creating the controls.

  • The Old New Thing

    Adventures in product testing: Candles that catch fire

    • 8 Comments

    Not to be outdone by frying pans that explode when you use them for frying, Nature's Finest Candles has issued a product recall [pdf] because the candles catch fire when you light them.

    This is not to be confused with birthday candles that catch fire and explode.

  • The Old New Thing

    The dialog manager, part 1: Warm-ups

    • 21 Comments
    I think a lot of confusion about the dialog manager stems from not really understanding how it works. It's really not that bad. I'll start by describing how dialog boxes are created over the next few articles, then move on to the dialog message loop, and wrap up with some topics regarding navigation. There will be nine parts in all.

    The first major chunk of the dialog manager has to do with reading the dialog template and creating a dialog box based on it.

    All of the CreateDialogXxx functions are just front-ends to the real work that happens in CreateDialogIndirectParam. Some of them are already visible in the macros: CreateDialog is just a wrapper around CreateDialogParam, with a parameter of zero. Similarly, CreateDialogIndirect is just a wrapper around CreateDialogIndirectParam with a zero parameter.

    Here's a slightly less trivial wrapper:

    HWND WINAPI CreateDialogParam(HINSTANCE hinst,
        LPCTSTR pszTemplate, HWND hwndParent,
        DLGPROC lpDlgProc, LPARAM dwInitParam)
    {
      HWND hdlg = NULL;
      HRSRC hrsrc = FindResource(hinst, pszTemplate,
                                 RT_DIALOG);
      if (hrsrc) {
        HGLOBAL hglob = LoadResource(hinst, hrsrc);
        if (hglob) {
          LPVOID pTemplate = LockResource(hglob); // fixed 1pm
          if (pTemplate) {
            hdlg = CreateDialogIndirectParam(hinst,
                     pTemplate, hwndParent, lpDlgProc,
                     dwInitParam);
          }
          FreeResource(hglob);
        }
      }
      return hdlg;
    }
    

    All CreateDialogParam does is use the hinst/pszTemplate to locate the lpTemplate, then use that template in CreateDialogIndirectParam.

    Okay, this was easy. Tomorrow, we're going to create the dialog from the template.

  • The Old New Thing

    When a program asks you a question and then gets upset if you answer it

    • 33 Comments

    JeffDav's story of a program that didn't like it when he told it where to install reminded me of another program that we dealt with during Windows 95 development.

    This was a big-name program developed by one of the biggest-of-the-big name software companies. Let's give this program the imaginary name "LitWare". Its setup program asked you where you wanted the program to be installed, and it suggested "C:\LITWARE". If you accepted the default, then everything proceeded normally.

    However, if you changed the default to anything else, the setup program ran to completion, but the program itself wouldn't run.

    Because the program contained the hard-coded path "C:\LITWARE" and insisted that it find its support files in that directory.

    At least Jeff's program realized that it was about to be installed into a directory where it would fail to work!

  • The Old New Thing

    The CEO-to-English Phrase Book, a continuing series from Slate

    • 10 Comments

    I'm an economics geek, so of course I'm a fan of Slate's Moneybox column as well as The Dismal Science and most of all, Steven Landsburg's gleefully provocative Everyday Economics. I'm also a language geek, so I've been quite enjoying the occasional Moneybox articles which decode CEO speak into plain English titled The CEO-English Phrase Book, thereby combine two geek topics into one.

    Terms that have been defined so far:

  • The Old New Thing

    If you disable drag/drop on the Start menu, you also disable right-click

    • 26 Comments

    This is one of those poorly-worded options.

    In the Start menu configuration dialog, you can choose to uncheck "Enable dragging and dropping". This setting disables drag/drop but also disables right-click context menus. The connection between the two is explained in the Group Policy Editor, but is unfortunately oversimplified in the general-public configuration dialog.

    Why does disabling dragging and dropping also disable context menus?

    History, of course.

    Originally, the "Disable drag/drop on the Start menu" setting was a system policy, intended to be set by corporate IT departments to prevent their employees from damaging the Start menu. With this setting, users could no longer drag items around to rearrange or reorganize their Start menu items. This is a good thing in corporate environments because it reduces support calls.

    But very quickly, the IT departments found a loophole in this policy: You could right-click an item on the Start menu and select Cut, Copy, Paste, Delete, or Sort by Name, thereby giving you access to the operations that the policy was trying to block. Therefore, they requested that the scope of the policy be expanded so that it also disabled the context menu.

    In Windows XP, it was decided to expose what used to be an advanced deployment setting to the primary UI, and so it was. Since it's the same setting, it carried the loophole-closure with it.

  • The Old New Thing

    The invisible price reduction

    • 17 Comments

    Swedish discount warehouse chain Coop Forum is running an advertising campaign claiming "New Lower Prices!", but how can you tell? Apparently you're not supposed to (shocking!) compare current prices against what they were before the ad campaign.

    Raymond's bad translation follows.

    Coops "new" price reduction not new

    A major new price reduction advertising campaign issued by discount warehouse chain Coop Forum this week to all households in the Stockholm area could be misleading.

    The list of which merchandise which actually had their prices lowered, that is secret, according to Roger Gehrman, vice managing director for Coop Forum.

    "The price structure is one of the probably most important trade secrets we have," he says.

    But you claim that you are lowering prices. How can one be sure that you really are doing it?

    "You can't compare today's prices with how they were earlier," says Roger Gehrman.

    Difficult to know for both Ica and Coop

    It is almost just as hard to get a grasp on Coop's claimed price reduction as it is with Ica's.

    Ekot's check shows that both chains refuse to show which individual items were lowered or how large individual price reductions are.

    Don't want to report which items were reduced

    Coop's manager Roger Gehrman doesn't even want to state which product categories were reduced the most.

    "I don't want to go into that. It is nearly all of our product range but I don't have detailed knowledge to talk about a specifically reduced product and I don't have the list in front of me either," he says.

    This week's advertising flyer from Coop Forum to households in the Stockholm region are misleading. "Welcome to our largest price reduction ever" and "New lower prices" says the discount warehouse chain now about a price reduction which in the Stockholm area was already put into place last autumn.

    "It happened at the end of October 2004," says the Coop Forum manager.

    One figure Coop management did release, and that's how much the 7,000 items were reduced on average: five percent.

  • The Old New Thing

    Pointers to virtual functions with adjustors

    • 8 Comments

    As a mental exercise, let's combine two mind-numbing facts about pointers to member functions, namely that all pointers to virtual functions look the same and that pointers to member functions are very strange animals. The result may make your head explode.

    Consider:

    class Class1 {
     public: virtual int f() { return 1; }
    };
    
    class Class2 {
     public: virtual int g() { return 2; }
    };
    
    class Class3 : public Class1, public Class2 {
    };
    
    int (Class3::*pfn)() = Class3::g;
    

    Here, the variable pfn consists of a code pointer and an adjustor. The code pointer gives you the virtual call stub:

     mov eax, [ecx]             ; first vtable
     jmp dword ptr [eax]        ; first function
    

    and the adjustor is sizeof(Class1) (which in our case would be 4 on a 32-bit machine). The result, then, of compiling a function call (p->*pfn)() might look something like this:

     mov ecx, p
     lea eax, pfn
     add ecx, dword ptr [eax+4] ; adjust
     call dword ptr [eax]       ; call
    -- transfers to
     mov eax, [ecx]             ; first vtable
     jmp dword ptr [eax]        ; first function
    -- transfers to
     mov eax, 2                 ; return 2
     ret
    

    Okay, I lied. It's really not all that complicated after all. But you can probably still impress your friends with this knowledge. (If you have really geeky friends.)

Page 1 of 4 (34 items) 1234