Woody20
Woody20

Reputation: 867

C++ Windows API - How to retrieve font scaling percentage on Windows 10

My goal is to be able to adjust the font size at runtime in a C++ app, based on the monitor resolution.

In this question, it's explained how to get the font scaling percentage, but the suggested function GetScaleFactorForMonitor requires Windows 8.1. My C++ app must run on Windows 7 or higher. I have tried several proposed solutions based on getting the ratio of device caps parameters, but they all get 1.0 on a Windows 10 system where the Windows "Make everything bigger" setting is 150%.

Per the MS docs, Visual Studio is adding "dpiAware" to the manifest (this is a setting I can change). Probably because the app is being built on a Windows 7 system, the VS-generated manifest does not include Windows 10 as a supported O/S. If I add the lines

       <!--This Id value indicates the application supports Windows 10, Windows Server 2016 and Windows Server 2019-->
      <supportedOS Id="{{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>

to the manifest, when I run the app on Windows 10, I get a "side-by-side" error.

How can I get this "make everything bigger" and the "make text bigger" settings on a Windows 7 or 10 system?

Build system: 64-bit Windows 7, Visual Studio 2019 16.7.7 Run system: Windows 7 or later

Upvotes: 0

Views: 1569

Answers (1)

dialer
dialer

Reputation: 4835

After discussing in the comments, it turns out your actual problem is that you don't declare DPI awareness in your manifest correctly.

You need to merge this into your manifest:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    </windowsSettings>
  </application>
</assembly>

Explanation:

dpiAware true is recognized by Windows 7 and Windows 10, and declares system-wide DPI awareness (DPI_AWARENESS_SYSTEM_AWARE). System-wide means that it assumes that all applications on all monitors use the same DPI setting).

dpiAwareness is recognized by Windows 10 (Windows 7 ignores it) and, if present, supersedes dpiAware. It declares per-monitor DPI awareness (DPI_AWARENESS_PER_MONITOR_AWARE), which means that each monitor can have different DPI settings and your application must handle that correctly.(2)

If you do not include this manifest, Windows will virtualize the DPI, meaning it will act as if the DPI setting is always at 96 (100%), and then Windows merely scales the bitmaps (blurry). This is a compatibility measure that ensures that applications which do not implement DPI code can still appear bigger.


Then, on Windows 7, you get the scaling factor using the GetDeviceCaps function with LOGPIXELSX and LOGPIXELSY, and dividing the result by 96 (because 96 dpi is "100%").(1) This will give you the DPI setting of the main monitor. GetDeviceCaps has been the way to get this setting since Windows XP. This will also do fine on Windows 10 if and only if you do not declare DPI awareness per monitor.

On Windows 10, if you declare DPI awareness per monitor, GetDeviceCaps will not suffice because it only returns the DPI setting for the main display. But if you declare PerMonitorV2, then you are obliged to implement per-monitor DPI correctly. To do this, you can call GetDpiForWindow, or MonitorFromWindow + GetDpiForMonitor.

Since you want your executable to run on both Windows 7 and Windows 10, you cannot link against GetDpiForWindow and GetDpiForMonitor because those functions do not exist in Windows 7. You will need manually link at runtime using GetProcAddress.


To merge the manifest, you can use the Manifest Tool in Visual Studio (Project Properties -> Manifest Tool). Put the entire manifest XML text from above into a file (e.g. DpiAwareness.manifest), and specify that under Manifest Tool -> Input and Output -> Additional Manifest Files.


As for the "Make text bigger" accessibility setting: It is a relatively new WinRT setting that is meant for UWP apps. You're not really expected to use it in Win32 applications, so it's going awkward in Win32. I can't help you there because I hate all things UWP. UWP can go die in a fire.


(1) I have never seen a DISPLAY device with a non-1:1 aspect ratio though. It is probably only useful for printers. The fact that GetDpiForWindow, which is the most modern of the mentioned functions, only returns one number, suggests that it is probably safe to assume that the DPI in X direction will always be equal to the DPI in Y direction (on DISPLAY devices).

(2) Note that there is also dpiAwareness PerMonitor (without V2). This is more or less a now-obsolete hack that came with Windows 8. Don't bother with it.

Upvotes: 1

Related Questions