Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

What’s wrong with this code, Part 22 – the answers

What’s wrong with this code, Part 22 – the answers

Rate This
  • Comments 14

The other day, I wrote about measuring the dimensions of a piece of text using the DrawText API.

My code worked just great when I initially tested it (obviously it’s a part of a larger chunk of code that does more complicated work).  The problem showed up when I started testing it on a machine running in High DPI mode (144DPI).

The code in question measured some text and then used that text to set the size of a button, when working in low DPI mode (96DPI), the font that is chosen had font metrics that closely matched the font that was used when painting the button.  The problem was that the button font that was used in high DPI mode had dramatically larger font metrics than the low DPI font. 

As a result, the rectangle returned by the initial DrawText call didn’t match the rectangle that was used when the button was painted.  That meant that the button text overflowed the button.

 

The fix to the code is to retrieve the font that is going to be used to draw the button and to use SelectObject to set the font in the memory DC to match the button font.  Once I made that change, the button drew perfectly.

Here’s the corrected code (new code in red):

   HDC hdc = CreateCompatibleDC(NULL); 

   RECT rcText = {0, 0, 88, 34}; 

   HFONT hFont = reinterpret_cast<HFONT>(SendMessage(m_hWndControl, WM_GETFONT, 0, 0)); 
   HFONT hFontOld = reinterpret_cast<HFONT>(SelectObject(hdc, hFont)); 

   DrawText(hdc, L"My Text String", -1, &rcText, DT_CENTER | DT_END_ELLIPSIS | DT_EDITCONTROL | DT_WORDBREAK | DT_NOPREFIX | DT_CALCRECT); 

   CAtlString string; 
   string.Format(L"Text String occupies: %d x %d pixels", rcText.right - rcText.left, rcText.bottom - rcText.top); 
   MessageBox(hWnd, string, L"String Size", 0); 

   SelectObject(hdc, hFontOld); 
   DeleteDC(hdc);

 

And yeah, I’m sure this is old-hat to experienced Windows UI programmers, but it stumped me for several hours so I figured I’d write it up for the blog in case someone else hits the same problem.

Kudos and omissions:

Aaron Ballman caught that I forgot to call DeleteDC when I was done with using the DC.  Stupid boneheaded mistake.

Shog9 and Ivo caught the root cause (selecting the wrong font).

 

And Eldan caught the Large Fonts implication.  There are other ways of catching this, but HighDPI is the easiest.

I also agree with his assertion that font handling of GDI is “complicated”.  On the other hand, the alternative is adding a dozen parameters to the DrawText API (you’d have to add foreground color, background color, font, etc to the call, which would complicate the API dramatically). 

 

PS For those who care about such things: When I’m testing UI changes, I start with standard DPI, then I retest the changes in HighDPI and again using high contrast mode.  Testing in high contrast mode also tests the code in a non themed mode, which helps to catch bugs where I accidentally depended on the theming logic.

  • This may seem like a stupid question, however, why are you guys not using WPF to do this?

  • Richard: WPF == Managed code.  Very little of the UI in Windows is written in managed code.

  • Dozens of extra parameters?  No, no, no.

    The problem here is that there is the DC has implicit state (current font, etc).  This state can be separated out and passed as a single extra parameter.

    This would allow a whole host of things to be simpler, like saving old state, using the same state every time, etc.

    Remember boys: implicit state (=> "global variables") is bad.

    explicit state ("pure functions") are good.

  • Why not use GetDC(m_hWndControl) directly?  DT_CALCRECT prevents anything from getting painted in the first place.

  • Don't forget to test with ClearType on and off as well.  ClearType can have subtle effects on your font sizes as well, though they appear to affect GDI+/managed drawing more than straight GDI.

  • @TimHollebeek: the "extra state" *is* already split out and passed as a separate parameter.  It's the HDC parameter.

    (And as for saving old state, you can use SaveDC/RestoreDC for that purpose.)

    @Ben: Using GetDC won't necessarily work.  That returns a DC compatible with the window and clipped to its bounds, but not necessarily with the control's font / colour scheme selected.  (If it's using a custom font, then it'll almost certainly *not* be selected.)

  • Technically speaking, if you're writing UI inside of MS, you should not use GDI. WPF would be good, and if that's no option, one of the internal frameworks. Same applies on the outside

  • "me": You're wrong.  I have no idea where you work, but in my division we absolutely do NOT use WPF.  The same is true for almost all of the Windows division.

    For various reasons the volume control UI doesn't use one of the internal frameworks (unless you count ATL as an "internal framework").  

  • Hi Larry,

    Is there a good reason to initialize rcText this way ?

    RECT rcText = {0, 0, 88, 34};

    Thanks for this interesting post.

  • Isn't quite a bit of the non-trivial UI done in HTML, via embedded WebBrowser controls? I know the XP Help and Support Center does that.

  • Hey Larry,

    This is unrelated but please answer this. What ASUS seem have done is they've implemented DS3D in software as part of their Xonar drivers and MS's latest effort XAudio 2 also is a software-based DSP/surround sound solution however it's RTM in March isn't well publicized by MS, as a result, very few developers are aware of it or use it. My question is technically, would it have been possible for Microsoft to implement DS3D in software so that all the surround effects would have worked? If so, if there were time constraints with Windows Vista, why can't the same be achieved with Windows 7? Or is it not technically possible with the new audio stack?

  • Ben: Raymond Chen just sent me an email suggesting the same thing.  I hadn't realized that DT_CALCRECT would suppress drawing.

  • Hi Larry,

    why don't you use a WPF-like technology in Windows UI?

    I think that WPF is based on Direct3D (which is a native technology), with an interface layer to export native D3D stuff to managed world (maybe you use C++/CLI ?). So I wonder: what about using some kind of "WPF" in a 100% native context i.e. Direct3D *hardware accelerated rendering* using a *declarative* approach (like WPF does) for Windows UI?

    ...Performance reasons?

    BTW: Thank you for your interesting blog posts! (Especially this "What's wrong in code" series - they help us learning a lot).

    Regards

  • Sys64738: Two words: "Managed Code".  There are severe architectural layering issues with using extremely high level components like WPF.  Server administrators will get hideously upset if they had to get WPF on their server machines just because the volume control UI used it.

    The CLR team is improving things regularly and there is more and more managed code in Windows but the vast majority of Windows code is unmanaged.

Page 1 of 1 (14 items)