The life of desktop "old school" programmers in degrees got more complicated at each step of heightened display resolutions. Fixed icon sizes and magic constants had to be replaced with DPI-aware code and multiple resources that would cater for any resolution the program happened to run. All measurements had to be scaled to the prevailing DPI. For multi-monitor setups, even this isn't enough, as DPI can vary depending on the monitor too.
Microsoft realized that it would be hard for old tools to be converted for dynamic DPI environments, so it applied various automatic scaling techniques to make 16x16 pixel toolbars visible in UHD monitors. Stretched windows are bearable but are a fuzzy disgrace in a sleek high resolution screen. Even programs that are declared <dpiAware> through a manifest will look good only in the primary display, and an abomination everywhere else.
Figure 1. Pixelated window stretched to high DPI
Then we add a handler for WM_DPICHANGED in our main window. This message is sent only if our manifest includes <dpiAwareness>=permonitorv2. Child windows don't get this message but I suspect all common controls handle WM_DPICHANGED_BEFOREPARENT, that's how they change their fonts before our WM_DPICHANGED handler. In there, we pass the notification to any custom controls we manage and do any other necessary actions for the new DPI.
Oddly, WM_DPICHANGED only tells us of the new DPI, so we must keep track of the old one in a member variable to calculate the DPI scale factor (new/old)
When updating old code to be permonitorv2 compliant, it's best to search the code base for fixed DPI culprits like GetDeviceCaps (especially LOGPIXELSY), GetSystemMetrics (especially for icon sizes SM_CXICON etc) and SystemParametersInfo (especially for menu font sizes). Also dig up "magic constants" like #define PADDING 2 in your custom controls. Some of these can be tolerated under "cut some corners" philosophy, but others must be dealt with to use the correct DPI scale. Below you will find a list that I dealt with for zabkat programs:
1. FONT MAPPING. Fonts for custom controls should be kept as point sizes (DPI independent), and adjusted for the correct pixel height for a window according to this formula (1 inch == 72 points)
LOGFONT.lfHeight = MulDiv(GetDpiForWindow(hwnd), pointSize, 72);Another option is to use SystemParametersInfoForDpi for a suitable SPI_GETICONTITLELOGFONT size. Or if you are getting a font variation for listview custom drawing (e.g. an italic font), use WM_GETFONT to grab the updated common control font, then modify it (remember all windows controls have already updated internally by the time we receive WM_DPICHANGED).
2. TOOLBAR CONTROLS. If your buttons show text, your best bet is to delete and reinsert all of them, otherwise BTNS_AUTOSIZE doesn't work half of the time and you end up with cropped text or oversize buttons. If your toolbars host child combo or edit controls, whose height may change due to font resizing, you need to reposition them and adjust the buttons' vertical size to accommodate them (TB_SETBUTTONSIZE). Ideally you should also change the button imagelists if you can be bothered to load bigger icons. Sorry I couldn't be bothered :)
3. REBAR CONTROLS. As the toolbars they hold got resized (step 2 above), the rebar bands must be resized too, in terms of height (cyMinChild) and preferred width (cxIdeal) so that any chevrons show correctly
4. STATUS BAR. Any status panes must be repositioned and sized with SB_SETPARTS to match the latest font.
5. SETTINGS PERSISTENCE. For robustness, all window sizes and positions, font sizes and all DPI sensitive information should be saved in a DPI independent fashion (e.g. assuming fixed 96 DPI) and then mapped to the actual DPI when restoring the program window. Of course that's too much work for legacy code, so at least you should make sure the window is created in the same monitor where it was last saved (registry settings persistence). Most likely the DPI won't have changed and all will be well. But if the user changes desktop text size while the program is not running, then all saved settings will be smudged.
There will be numerous odds and sods that must be resized, e.g. the dimension of thumbnails, any other pixel size constants etc. But these will depend on your program. Most of the remaining "big stuff" will be supplied automatically by the framework:
Post a comment on this topic »