4

I'm manually creating a dialog in memory using the DLGTEMPLATEEX structure. I always want the dialog controls to use the default Shell font, so I set DS_SHELLFONT. MSDN says that if DS_SHELLFONT is set, the "system selects a font using the font data specified in the pointsize, weight, and italic members". And MSDN also says that I have to set typeface to "MS Shell Dlg" if I set DS_SHELLFONT. Raymond Chen says that this apparent redundancy is due to compatibility reasons.

So this is all fine with me but there's one thing that is confusing me: Why do I have to set pointsize as well? Doesn't that somehow contradict the whole idea of the system using the default Shell font and size? My application is now DPI aware and I assume that on systems with a higher DPI than 96 the Shell font will also be larger. Thus, hard-coding a specific font size in pointsize doesn't really make sense to me at all.

So what's the point of having pointsize at all when setting DS_SHELLFONT? What should I do with it? Should I simply set pointsize to 0? But MSDN doesn't say that it can be set to 0. It reads as if I need to pass a specific font size here but this seems like a contradiction to the whole DS_SHELLFONT idea to me, or did I get something wrong here?

Andreas
  • 9,245
  • 9
  • 49
  • 97
  • In 2017, why would you want to use `DS_SHELLFONT`? – David Heffernan Jan 03 '17 at 16:49
  • `DS_SHELLFONT` is basically obsolete. Although you still need to continue specifying it, [it doesn't *actually* give you the current shell font](http://stackoverflow.com/questions/6057239/which-font-is-the-default-for-mfc-dialog-controls/6057761#6057761). You need to obtain that font's logical information (`NONCLIENTMETRICS.lfMessageFont`) and use *those* attributes when dynamically creating your dialog template. Things are much easier for you since you *are* dynamically creating the dialog template. – Cody Gray - on strike Jan 03 '17 at 16:54
  • Afaict, the assumption that there is a "default size" for the font is just wrong. – Hans Passant Jan 03 '17 at 17:58
  • @CodyGray: If that is the case, wouldn't it be possible to just use `DS_SETFONT` instead of `DS_SHELLFONT`? Then I wouldn't have to use `DLGTEMPLATEEX` at all and I could just stick with `DLGTEMPLATE`. – Andreas Jan 03 '17 at 18:39
  • Well, maybe you don't need `DS_SHELLFONT`, I can't remember the details. But even if you use only `DS_SETFONT`, you will still need `DLGTEMPLATEX`. Not is it required by that flag, but you actually *want* it because this is the way you need to dynamically set the font. – Cody Gray - on strike Jan 03 '17 at 18:45
  • I don't understand. `DS_SETFONT` doesn't require `DLGTEMPLATEEX`. You can use `DLGTEMPLATE` for that and just write the point size and the face name after `DLGTEMPLATE`. Check MSDN. It says: "When the DS_SETFONT style is specified, 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" https://msdn.microsoft.com/de-de/library/windows/desktop/ms645394(v=vs.85).aspx – Andreas Jan 03 '17 at 18:50
  • You've fallen into a common fallacy about what being DPI aware means. Before I explain that, I'll clear up your confusion about font point sizes. The point size you specify for fonts is still based on the traditional 72 dpi metric: a 72 point font will take 1 inch of vertical space, even on a 96 dpi display. – andlabs Jan 03 '17 at 19:09
  • 1
    Once that is out of the way, the true meaning of DPI awareness now becomes clear: Being DPI aware does not mean that small things suddenly become big. Being DPI aware means that if I put two screens with different DPI side by side and run my program on both screens, then take a ruler and measure the physical size in the real world of the windows on both screens, they will have the same physical size. What high DPI gives you is better detail: more pixels per inch means more accuracy in drawing vector graphics. – andlabs Jan 03 '17 at 19:11
  • [This image](https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Art/backing_store_2x.png) and [this image](https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Art/sharper_drawing_2x.png), both from Apple's guide to high DPI programming on OS X, should make the difference clear. – andlabs Jan 03 '17 at 19:15
  • As another example, [here's a screenshot of a GTK+ thing I was writing a while ago](http://i.imgur.com/3ZtlLKR.png). The window on the right is drawn at 5x the scale of the window on the left. You should imagine that if I had a 5x96dpi screen, the window on the right would have the same physical size as the window on the left, but with the detail of the window on the right. (I need to make all of this into a reusable answer template...) – andlabs Jan 03 '17 at 19:15
  • @andlabs: Thanks for the explanation. But that still leaves the problem which size to choose for `pointsize`, doesn't it? AFAIU, Cody suggests to simply use `NONCLIENTMETRICS.lfMessageFont.lfHeight` as `pointsize`. It looks like a hack but reading [Cody's detailed investigation](http://stackoverflow.com/questions/6057239/which-font-is-the-default-for-mfc-dialog-controls/6057761#6057761) makes it look like there is no other way than to steal the message box font. But there is also still the question as to whether `DS_SHELLFONT` should be used at all or if I should just use `DS_SETFONT`. – Andreas Jan 03 '17 at 19:42
  • You can't just use the `lfHeight` directly in a dialog template because [the dialog template treats the point size as an actual point size](https://blogs.msdn.microsoft.com/oldnewthing/20040618-00/?p=38803). [The `LOGFONT` structure documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/dd145037(v=vs.85).aspx) tells you how to convert from point size to LOGFONT size, but you then have the chicken-and-egg problem of not being able to convert the point size until after you have a DC, which doesn't happen until after you have the window. – andlabs Jan 03 '17 at 19:54
  • (You could probably get away with using the owner window's DC, and [judging from the ordering Raymond Chen says that events in dialog creation happen](https://blogs.msdn.microsoft.com/oldnewthing/20050330-00/?p=36023) this might actually be the case in real Windows, but then per-monitor DPI awareness might screw things up. I wouldn't know.) – andlabs Jan 03 '17 at 19:55
  • Oh my, now the confusion is complete. Why is this so complicated? Heck, I just want to create a dialog with the system's default GUI font that scales nicely on higher DPI systems. It's insane that this should be so complicated with so much speculation and no definite answers. It must be possible in C without using any toolkits. So what should I do now? Am I really the only one with this problem? That's hard to believe... – Andreas Jan 03 '17 at 20:00
  • Now I'm confused: are you or are you not using a dialog template? My comments assumed you were using a dialog template. If you aren't using a dialog template, then you can just use the code Cody Gray posted to do everything in code. – andlabs Jan 03 '17 at 20:16
  • Yes, I'm using a dialog template, not a resource compiled by RC. Instead, I'm sticking together my dialog entirely in memory by filling out `DLGTEMPLATE` and `DLGITEMTEMPLATE` in memory and then calling `DialogBoxIndirectParam()` to show it. – Andreas Jan 03 '17 at 20:20
  • Fancy that, [I wound up looking into the device context used by the non-client metrics for another question just a few days later](http://stackoverflow.com/questions/41505151/how-to-draw-text-with-the-default-ui-font-in-directwrite#41505750). So you can use `GetDC(NULL)` to perform the calculation to convert from `lfHeight` to points and fill out your dialog template. – andlabs Jan 06 '17 at 23:44
  • Are you sure that I need to convert from `lfHeight` to points at all for the dialog template? Yes, I've seen R. Chen's post you linked to above but my tests have shown that when setting the font size in the dialog template to `-MulDiv(NONCLIENTMETRICS.lfMessageFont.lfHeight, GetDeviceCaps(hDC, LOGPIXELSY), 72)` my dialog controls appear in a huge version of Segoe UI. When just setting the font size in the dialog template to `NONCLIENTMETRICS.lfMessageFont.lfHeight` ,however, everything is fine. `lfHeight` is -12. When converted to points using the formula above, it is 16. – Andreas Jan 07 '17 at 20:32
  • Oops, sorry, of course it has to be `-MulDiv(NONCLIENTMETRICS.lfMessageFont.lfHeight, 72, GetDeviceCaps(hDC, LOGPIXELSY))` to go from `lfHeight` to points. – Andreas Jan 07 '17 at 21:16
  • The reason this is so complicated is that Windows was *not* designed with high-DPI support. And it *certainly* wasn't designed with what they've very recently introduced, *per-monitor* high-DPI support. The answer of mine that you cite was *not* written with per-monitor DPI support in mind, and neither is any of my code. It remains broken in Windows and continues to be impossible to implement. [They finally fixed one of the bugs in Windows 10 Anniversary Edition](http://stackoverflow.com/q/36864894) that prevents apps from implementing this, but it's still not complete. – Cody Gray - on strike Apr 27 '17 at 05:21

1 Answers1

3

TL;DR The direct answer to your question is 8.

The goodwilled answer is to use NONCLIENTMETRICS.lfMessageFont, as it is, and don't change anything about it (except DPI-scaling the lfHeight, if you decide to do that yourself instead of using SystemParametersInfoForDpi).

TL;DR End.

To answer the question about the point size of "The Default Font", note first that there are multiple possible definitions of what The Default Font could be, and the definition you chose is "the DS_SHELLFONT definition".

Then question becomes, in the dialog template, what point size goes in the FONT statement (this is the same concept regardless of whether the dialog was built with an .rc file, or in code):

STYLE DS_SHELLFONT | ...
FONT <WHAT GOES HERE?>, "MS Shell Dlg", 0, 0, 0x1
[...]

Here are some things you need to know:

  • There's no good answer because Microsoft screwed up.
  • DS_SHELLFONT causes MS Shell Dlg to be replaced with MS Shell Dlg 2 upon dialog creation (i.e. somewhere in the whole CreateDialog call chain).
  • MS Shell Dlg 2 maps to Tahoma using FontSubstitutes in the registry, regardless of locale. That has been the case for its entire existence, and that will probably never change, at least not in the foreseeable future.
  • Everyone else uses 8 because it's the default in Visual Studio.
  • Windows built-in dialogs don't use DS_SHELLFONT. For each locale, they use a specific font face and font size that is appropriate for that locale, hard coded in the locale's MUI resource files (they have a handcrafted dialog template for each language). For western systems, the font is MS Shell Dlg 8pt. This may give you the false impression that MS Shell Dlg is universally applicable (since it's a registry mapped pseudo-font), and that you can distribute a program that uses it at a fixed size of 8pt internationally. But no. On Japanese systems for example, the thing that's hard-coded into the dialog templates is MS UI Gothic, 9pt (note that it is not MS Shell Dlg even though on a Japanese system that would have been mapped to MS UI Gothic). If you had used MS Shell Dlg 8pt here, it would have been substituted to MS UI Gothic 8pt, which is simply too small for Japanese text. The exact details are more complicated and beyond the scope of this answer.
  • Some of the replacement fonts for MS Shell Dlg are so bad (e.g. MS UI Gothic) that even latin text looks awful at 8pt - it's only usable at 9pt.
  • WinForms applications (.NET Framework <= 4.8) use GetStockObject(DEFAULT_GUI_FONT), which returns a HFONT with MS Shell Dlg. .NET >= 5 uses NONCLIENTMETRICSW.lfMessageFont. So if you want your thing to blend in here, DS_SHELLFONT is not the way to go either way.

So basically, using MS Shell Dlg, hardcoded at 8pt, without DS_SHELLFONT, is an error, regardless of what you're doing. It only works out if you know basically already know exactly that it's going to be replaced with a font which is compatible with the specific script you are using in your text, at a point size of 8pt.

MS Shell Dlg without DS_SHELLFONT leaves another issue: If your application is in some Western language, and you are lucky that MS Shell Dlg does indeed resolve to a font that works at 8pt with Western script, but the user decides to enter Chinese or Japanese text into a text box, then the text in the text box will be too small. Font fallback estimates font sizes according to the font height in pixels, which is not always appropriate. You would have to change the font according to what script is being entered, which is not something anybody wants to do because it would require exhaustive research of all possible languages. (Coincidentally, this is also a huge blunder in CSS, which allows you to define fallback font faces, but not the fallback font sizes.)

If DS_SHELLFONT is set, both of these problems just happen to be alleviated in practice:

  • Because Tahoma at 8pt is good for Western scripts.
  • Because the default font fallback for Tahoma tends to behave better (it just happens to be bigger).

In principle, MS Shell Dlg 2 has the same problem as MS Shell Dlg, in that theoretically it could be mapped to a font which requires a different size to be displayed correctly. But because MS Shell Dlg 2 likely always will be Tahoma, and because basically every dialog created with the dialog editor in Visual Studio defaulting to size 8, it is most likely safe to assume that Microsoft will never break this. So the font you want to put there is MS Shell Dlg, 8pt, and DS_SHELLFONT will translate that to, effectively, Tahoma 8pt. That 8 is hard-coded in practically everyone's dialog template.

What do I mean by "Microsoft screwed up"? Well we shouldn't be in this complicated situation. Basically by using a dynamic pseudo-font like MS Shell Dlg 2 you're saying I'm totally ready for the default font to change any minute now, and if it does, I'll still blend in with all the cool kids who used the correct defaults! But the truth is, you didn't use anything like a default font. You only used some default font face. If some Windows GUI designer ever did decide that it was time to change the default font for all applications, it would be important that they also have control over font size, weight, and style. The current system doesn't allow for that. Fonts are finicky, the individual characters of Segoe UI 9pt look more like those of a typical 8pt font, for example (Segoe UI is barely readable at 8pt). Not to mention that it wouldn't work anyway because Microsoft themselves hard-coded specific fonts for specific locales into their dialogs.

Which is why NONCLIENTMETRICSW.lfMessageFont is recommended everywhere now. It does allow exactly what a hypothetical Microsoft GUI designer would want. And you don't have to guess what the correct font size, weight, and style is. Only problem is, you can't put that in a dialog template, so it requires manual programming. But beware that it doesn't necessarily solve the problem of having a user enter foreign text in a text box -- Segoe UI, just like Tahoma, just happens to behave more nicely with font fallback so in practice it shouldn't be an issue anymore.

On a final note: If your program is not localized at all, it might be a better idea to hard-code a concrete font that looks good in English. Be it Microsoft Sans Serif, or Tahoma, or Segoe UI. That makes layout easier (you don't have to accomodate for the wildest font metric differences on different systems and user settings, etc.), and you can use the dialog template designer as-is. And it ensures that your beautiful English text doesn't get utterly defaced by the default MS Shell Dlg font on, say, a Chinese PC (it's really bad, man).

dialer
  • 4,348
  • 6
  • 33
  • 56
  • Thank you for this answer! One thing I haven't figured out: What fond + flags + sizes SHOULD go into a dialog resource? Or should I use `NONCLIENTMETRICSW` and set the font at runtime? – peterchen Sep 10 '21 at 09:42
  • 1
    @peterchen Not sure and I can't really give good advice on what's correct or best. I guess I would go with the naive approach of "MS Shell Dlg" with DS_SHELLFONT at 8pt (assuming there is only latin text). Not because it's a good solution, but because I'm lazy. The dialog designer doesn't understand dynamic fonts, so dynamically assigning a font at runtime is awkward if you want to use the designer. And you would have to WM_SETFONT all the individual controls, which isn't pleasant either (and another potential source of bugs). `NONCLIENTMETRICSW` might look more professional though... – dialer Sep 10 '21 at 18:01