views:

2076

answers:

6

Hi, I'm trying to determine how I can detect when the user changes the Windows Font Size from Normal to Extra Large Fonts, the font size is selected by executing the following steps on a Windows XP machine:

  1. Right-click on the desktop and select Properties.
  2. Click on the Appearance Tab.
  3. Select the Font Size: Normal/Large Fonts/Extra Large Fonts

My understanding is that the font size change results in a DPI change, so here is what I've tried so far.


My Goal:

I want to detect when the Windows Font Size has changed from Normal to Large or Extra Large Fonts and take some actions based on that font size change. I assume that when the Windows Font Size changes, the DPI will also change (especially when the size is Extra Large Fonts


What I've tried so far:

I receive several messages including: WM_SETTINGCHANGE, WM_NCCALCSIZE, WM_NCPAINT, etc... but none of these messages are unique to the situation when the font size changes, in other words, when I receive the WM_SETTINGSCHANGE message I want to know what changed.

In theory when I define the OnSettingChange and Windows calls it, the lpszSection should tell me what the changing section is, and that works fine, but then I check the given section by calling SystemParametersInfo and I pass in the action SPI_GETNONCLIENTMETRICS, and I step through the debugger and I make sure that I watch the data in the returned NONCLIENTMETRICS for any font changes, but none occur.

Even if that didn't work, I should still be able to check the DPI when the Settings change. I really wouldn't care about the other details, every time I get the WM_SETTINGCHANGE message, I would just check the DPI and perform the actions I'm interested in performing, but I'm not able to get the system DPI either.

I have tried to get the DPI by invoking the method GetSystemMetrics, also for each DC:

Dekstop DC->GetDeviceCaps LOGPIXELSX/LOGPIXELSY Window DC->GetDeviceCaps LOGPIXELSX/LOGPIXELSY Current DC->GetDeviceCaps LOGPIXELSX/LOGPIXELSY

Even if I change the DPI in the Graphic Properties Window these values don't return anything different, they always show 96.

Could anybody help me figure this out please? What should I be looking for? Where should I be looking at?

afx_msg void CMainFrame::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
 int windowDPI = 0;
 int deviceDPI = 0;
 int systemDPI = 0;
 int desktopDPI = 0;
 int dpi_00_X = 0;
 int dpi_01_X = 0;
 int dpi_02_X = 0;
 int dpi_03_X = 0;

 CDC* windowDC = CWnd::GetWindowDC(); // try with window DC
 HDC desktop = ::GetDC(NULL); // try with desktop DC
 CDC* device = CWnd::GetDC(); // try with current DC
 HDC hDC = *device; // try with HDC
 if( windowDC )
 {
  windowDPI = windowDC->GetDeviceCaps(LOGPIXELSY); 
  // always 96 regardless if I change the Font 
  // Size to Extra Large Fonts or keep it at Normal

  dpi_00_X = windowDC->GetDeviceCaps(LOGPIXELSX); // 96
 }

 if( desktop )
 {
  desktopDPI = ::GetDeviceCaps(desktop, LOGPIXELSY); // 96
  dpi_01_X = ::GetDeviceCaps(desktop, LOGPIXELSX); // 96
 }

 if( device )
 {
  deviceDPI = device->GetDeviceCaps(LOGPIXELSY); // 96
  dpi_02_X = device->GetDeviceCaps(LOGPIXELSX); // 96
 }

 systemDPI = ::GetDeviceCaps(hDC, LOGPIXELSY); // 96
 dpi_03_X = ::GetDeviceCaps(hDC, LOGPIXELSX); // 96

 CWnd::ReleaseDC(device);
 CWnd::ReleaseDC(windowDC);
 ::ReleaseDC(NULL, desktop);
 ::ReleaseDC(NULL, hDC);

 CWnd::OnWinSettingChange(uFlags, lpszSection);
}

The DPI always returns 96, but the settings changes DO take effect when I change the font size to Extra Large Fonts or if I change the DPI to 120 (from the graphics properties).

+1  A: 

When you call GetDeviceCaps() on the Desktop DC, are you perhaps using a DC that might be cached by MFC, and therefore contains out-of-date information? Are you making the GetDeviceCaps() call synchronously from inside your OnSettingsChange handler? I could see how either or both of these things might get you an out of date version of DPI.

Raymond Chen wrote about this and his solution looked like this (Note that I've added :: operators to avoid calling the MFC wrappers of the APIs):

int GetScreenDPI()
{
  HDC hdcScreen = ::GetDC(NULL);
  int iDPI = -1; // assume failure
  if (hdcScreen) {
    iDPI = ::GetDeviceCaps(hdcScreen, LOGPIXELSX);
    ::ReleaseDC(NULL, hdcScreen);
  }
  return iDPI;
}
Tim Farley
Yah, I'm attempting to do that, but it's not working :)...
Lirik
"Not working" in what way? Do you still get an unchanged DPI setting when you call the function as written above?
Tim Farley
+2  A: 

[EDIT after re-read] I'm almost positive that changing to "Large fonts" does not cause a DPI change, rather it's a theme setting. You should be able to verify by applying the "Large fonts" change and then opening the advanced display properties where the DPI setting lives, it should have remained at 96dpi.


DPI change is supposed to require a reboot. Maybe the setting hasn't propagated to a place where GetDeviceCaps can retrieve it?

Maybe try changing a non-reboot-requiring setting (resolution perhaps) and then see if you can detect the change. If you can, your answer is probably that you can't detect DPI change until after reboot.

Aidan Ryan
But changing the Font Size does not require a reboot and the changes take effect without rebooting... the system MUST be sending some sort of a message to the windows, else how would they know to change the font size?
Lirik
Sorry about the multiple re-edits, I just had to make sure I scrap any irrelevant information from the code... I tried some of your solutions, and I had to come back and re-edit so I have the most up to date comments here :) sorry again! Thanks for your help.
Lirik
+2  A: 

I have a hunch WM_THEMECHANGED will take care of you. It doesn't have any hinting about what changed, though. You'll have to use OpenThemeData and cache initial settings, then compare every time you get the message.

You probably don't need to care what changed though, can't you have a general-purpose layout routine that adjusts your form/dialog/whatever by taking everything into account and assumes starting from scratch?

What problem are you trying to solve?

Aidan Ryan
Long story, basically the original developers didn't realize that the windows font size would change and they didn't handle the situation gracefully when it does change :)... now I have to do it :). I'll try your suggestion and if it works I'll update you ASAP :).
Lirik
Font size change is just one of the cases you need to handle. You should really look at a layout engine like this one: http://www.codeproject.com/KB/MFC/UltimateToolbox_Layout.aspx. There's plenty of good ones on codeproject.
Aidan Ryan
A: 

I don't think the display DPI changes when the font size changes. Windows is probably just sending the WM_PAINT and WM_NCPAINT messages to all open windows, and they're redrawing themselves using the current (now large) system font.

jeffm
A: 

See http://msdn.microsoft.com/en-us/library/ms701681(VS.85).aspx , this is explained there (quote: "If you do not cancel dpi scaling, this call returns the default value of 96 dpi.")

A: 

Look at these values in the Registry:

Windows XP Theme HKCU\Software\Microsoft\Windows\CurrentVersion\ThemeManager\SizeName Possible values: NormalSize, LargeFonts, and ExtraLargeFonts These values are language-independent.

Windows Classic Theme HKCU\Control Panel\Appearance\Current Possible values: Windows Classic, Windows Classic (large), Windows Classic (extra large), Windows Standard, Windows Standard (large), Windows Standard (extra large) Note that these values are language-dependent.

Windows Vista doesn't support this feature. If we want a bigger font, simply change the DPI Setting. In that case, GetDeviceCaps should work.

Hope this helps.