In the win32 API, DPI doesn't actually mean "Dots Per Inch". Instead, it's just another way to represent the MonitorScale. I'll use the term "Win32DPI" to differentiate it from a real DPI. These two values have a one-to-one mapping:
Win32DPI = 96 * MonitorScale
MonitorScale = Win32DPI / 96
Since these values are just 2 different ways of looking at the same thing, if things get confusing you can always forget about one and just think about the other. MonitorScale is the value exposed to end users, controlled by going to "Display Settings" > "Scale and Layout" whereas Win32DPI is the value used by the win32 API. Monitor scaling was introduced to Windows to address programs becoming "too small" when resolution density increased. This means MonitorScale is always >= 100% which in turn also means Win32DPI >= 96.
The reason why 100% Monitor scale maps to a Win32DPI value of 96 is because this used to be the typical physical DPI of monitors. Nowadaws monitors regularly have physical DPI's over 300.
The Win32DPI/MonitorScale concept biforcates coordinates based on whether they are "DPI Aware". A "DPI Aware" coordinate maps to a pixel value on the screen, whereas a "DPI Unaware" coordinate can map to a square of 1 or more fractional pixels. The reason for doing this is that as monitors became more dense, programs written at a time when they were less dense became smaller. In some cases this made the program harder to use. To address this windows introduced the ability to scale your entire system up to accomodate these legacy programs without having to change them. Windows would take the pixels rastered by a program and scale them up afterwards. Note this has a performance cost and can make the program look "fuzzy". Windows also provided new APIs that allow a program to opt-in to use "DPI Aware" coordinate and handle scaling themselves. An application doing this can look more clean/crisp because it gives them access to all pixels. However, the developer needs to be careful about mixing coordinates that may or may not be "DPI Aware". Also, the DPI can change while the program is running so they should listen for changes and respond accordingly.
When updating a System DPI-aware application, some common steps to follow are:
- Mark the process as per-monitor DPI aware (V2) using an application manifest (or other method, depending on the UI framework(s) used).
- Make UI layout logic reusable and move it out of application-initialization code such that it can be reused when a DPI change occurs (WM_DPICHANGED in the case of Windows (Win32) programming).
- Invalidate any code that assumes that DPI-sensitive data (DPI/fonts/sizes/etc.) never need to be updated. It is a very common practice to cache font sizes and DPI values at process initialization. When updating an application to become per-monitor DPI aware, DPI-sensitive data must be reevaluated whenever a new DPI is encountered.
- When a DPI change occurs, reload (or re-rasterize) any bitmap assets for the new DPI or, optionally, bitmap stretch the currently loaded assets to the correct size.
- Grep for APIs that are not Per-Monitor DPI aware and replace them with Per-Monitor DPI-aware APIs (where applicable). Example: replace GetSystemMetrics with GetSystemMetricsForDpi.
- GetSystemMetrics, GetSystemMetricsForDpi
- AdjustWindowRectEx, AdjustWindowRectExForDpi
- SystemParametersInfo, SystemParametersInfoForDpi
- GetDpiForMonitor, GetDpiForWindow
- Test your application on a multiple-display/multi-DPI system.
- For any top-level windows in your application that you are unable to update to properly DPI scale, use mixed-mode DPI scaling (described below) to allow bitmap stretching of these top-level windows by the system.
High DPI Api Reference: https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-reference
Blog about how one project starte using per-monitor aware DPI: https://blog.marcocantu.com/blog/2018-nov-vcl-permonitorv2_getsystemmetrics.html
Question: The recommended window size sent with the WM_DPICHANGED message is too big
Solution: The problem occurs due to a Windows bug, which causes the new window size to be incorrectly calculated. The bug can be worked around by handling the WM_GETDPISCALEDSIZE message and calculating the new window size yourself.