What’s wrong with this code, Part 22 – the answers
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.
Comments
Anonymous
August 04, 2008
This may seem like a stupid question, however, why are you guys not using WPF to do this?Anonymous
August 04, 2008
The comment has been removedAnonymous
August 04, 2008
The comment has been removedAnonymous
August 04, 2008
Why not use GetDC(m_hWndControl) directly? DT_CALCRECT prevents anything from getting painted in the first place.Anonymous
August 04, 2008
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.Anonymous
August 04, 2008
The comment has been removedAnonymous
August 04, 2008
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 outsideAnonymous
August 04, 2008
"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").Anonymous
August 04, 2008
Hi Larry, Is there a good reason to initialize rcText this way ? RECT rcText = {0, 0, 88, 34}; Thanks for this interesting post.Anonymous
August 05, 2008
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.Anonymous
August 05, 2008
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?Anonymous
August 05, 2008
Ben: Raymond Chen just sent me an email suggesting the same thing. I hadn't realized that DT_CALCRECT would suppress drawing.Anonymous
August 06, 2008
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). RegardsAnonymous
August 06, 2008
The comment has been removed