WM_DPICHANGED constant

Requires: GfaWinx.lg32

Purpose

Constant that can be used in a MessageProc event sub to respond to DPI changes for a form.

Syntax

Const WM_DPICHANGED = 0x2E0

Description

The WM_DPICHANGED message is only sent in a dpi-aware application. An application is dpi-aware if it has a manifest that includes the dpi awareness tag or when the program uses the DpiAwareness function, see Remarks. The WM_DPICHANGED message is sent to a window if for that window the DPI has changed. The possible causes for the change in DPI is a window that is moved to a new monitor that has a different DPI or if the DPI of the monitor hosting the window changes.

Message parameters:

wParam The HIWORD of the wParam contains the Y-axis value of the new dpi of the window. The LOWORD of the wParam contains the X-axis value of the new DPI of the window. For example, 96, 120, 144, or 192.
The values of the X-axis and the Y-axis are identical for Windows apps.
lParam A pointer to a RECT structure that provides a suggested size and position of the current window scaled for the new DPI. The expectation is that apps will reposition and resize windows based on the suggestions provided by lParam when handling this message.

If the application processes this message, it should return zero. In the Form_MessageProc event sub set RetVal% to 0 and Valid? to True.

To properly scale the GFA-BASIC's graphical output commands both the form's scale and fontsize must be set. To draw the shapes according to the current DPI setting, the form's ScaleWidth and ScaleHeight properties are changed to reflect the current DPI. In the following example, the form's Scale* properties are initially set using the helper command ScaleXYWHToDpi frm, dpi, x0, y0, width, height. If the width and height parameters are omitted, the ScaleWidth is set to _X * (96/DPI) and ScaleHeight is set to _Y * (96/DPI). The example shows how to set ScaleWidth and ScaleHeight to a fixed number of units in X- and Y- direction. The coordinates must always be in 96 DPI units (the units as they are used on a 96 DPI display). So rather than using the Scale* properties in a DPI-aware program use ScaleXYWHToDpi.

To properly display the text, the fontsize as used on 96 dots-per-inch display is saved and scaled to the DPI using ScaleToDpiSng().

An application should not process WM_DPICHANGED when executing LoadFormPos and running the app from inside the IDE. The result of LoadFormPos might be a WM_DPICHANGED if the form is positioned on a different display with a different resolution than the IDE's display. To prevent the handling of WM_DPICHANGED test the gfawinx's exported variable LoadingFormPos in the WM_DPICHANGED handler, this variable is set to True when LoadFormPos is executing. This problem does not happen with a compiled to EXE application.

Example

'

' WM_DPICHANGED  (dpi-aware)

' Form.WhatsThisHelpID stores the current dpi

'

$Library "gfawinx"

DpiAwareness()

OpenW Hidden 1, 0, 0, 300, 300, ~15

' A form uses DEFAULT_GUI_FONT, but on hi-dpi display

' Windows might use a differently sized default font.

Me.FontSize = 8.25 * 96 / WinDpi(SysDpi)        ' reset to form's default

' TODO: perform initialization

FormScaleForDpi(Me)       ' for actual screen

LoadFormPos(Me)

Win_1.Show

Do

Sleep

Until Me Is Nothing

 

Sub Win_1_Destroy

SaveFormPos Win_1 ' \\HKCU\Software\UserName\AppName\Win_1Placement

EndSub

 

Sub Win_1_Paint

' GB's graphical commands use correct fontsize and scaling

Text 2, 2, "Hello DPI aware GB program"

Box 0, 0, 260, 30

EndSub

 

Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)

Local Int dpi

If Mess% == WM_DPICHANGED

If FormScaleForDpi(Me, wParam%, lParam%)

' TODO: update other dpi-dependent resources

dpi = LoWord(wParam%)

EndIf

retval% = 0 : ValidRet? = True    ' tell OS we handled it

EndIf

EndSub

' FormScaleForDpi is used in all dpi-aware samples

 

Function FormScaleForDpi(ByVal frm As Form, Optional newdpi As Int, Optional lParam As Int) As Bool

Local i As Int, c As Control, rc As RECT, prevDpi As Int, newrc As Pointer To RECT, _

tb As Tab, w As Int, h As Int

With frm

' Set initial newdpi for the form and the scale factors for a LoadForm

If .WhatsThisHelpID == 0                    ' WhatsThisHelpID stores the current dpi

If Exist(":{" + .Name)                    ' a Form-Editor form

.WhatsThisHelpID = WinDpi(0)            ' initial dpi is system newdpi

.ScaleWidth = .ScaleWidth * 96 / .WhatsThisHelpID   ' set initial scale factors

.ScaleHeight = .ScaleHeight * 96 / .WhatsThisHelpID

Else                                      ' created by OpenW or Form command

.WhatsThisHelpID = 96                   ' coord space is 96

EndIf

EndIf

prevDpi = .WhatsThisHelpID

newdpi = LoWord(newdpi)

If newdpi == 0 Then newdpi = WinDpi(.hWnd)

Exit Func If newdpi == prevDpi              ' don't continue if equal

' Size the form, except if it is a child window

If !(GetWindowLong(.hWnd, GWL_STYLE) %& WS_CHILD)

If lParam                                 ' size to the OS suggested size

Pointer newrc = lParam                  ' ptr to RECT in lParam

SizeW .hWnd, newrc.Right - newrc.Left, newrc.Bottom - newrc.Top

Else                                      ' scale based on the current client size

GetClientSize .hWnd, w, h               ' can't use _X _Y, they are for ME only

AdjustW .hWnd, Scale(w, newdpi, prevDpi), Scale(h, newdpi, prevDpi)

EndIf

EndIf

.FontSize = .FontSize * newdpi / prevDpi

.ScaleWidth = .ScaleWidth * prevDpi / newdpi

.ScaleHeight = .ScaleHeight * prevDpi / newdpi

' Scale position and size of the Ocx windowed controls

For Each c In frm.Controls

If !(TypeOf(c) Is ImageList || TypeOf(c) Is Timer || _

TypeOf(c) Is CommDlg || TypeOf(c) Is TrayIcon' not the windowless OCX-es

' First set fontsize, but not all OCX-es have a Font property

If !(TypeOf(c) Is ProgressBar || TypeOf(c) Is Slider || TypeOf(c) Is Image || TypeOf(c) Is RichEdit)

c.fontsize = c.fontsize * newdpi / prevDpi

ElseIf TypeOf(c) Is RichEdit            ' Requires LoadRichEdit50W!

SendMessage c.hwnd, WM_DPICHANGED, MakeLong(newdpi, newdpi), 0

EndIf

' Set new position and size for OCX-controls

GetWinPos(c.hwnd, rc)                   ' relative to parent

~SetWindowPos(c.hwnd, 0, _

Scale(rc.Left, newdpi, prevDpi), Scale(rc.Top, newdpi, prevDpi), _

Scale(rc.Right - rc.Left, newdpi, prevDpi), _

Scale(rc.Bottom - rc.Top, newdpi, prevDpi),  SWP_NOZORDER) ' new pos and size

If TypeOf(c) Is Form                    ' Scale OCX-es of a child Form Ocx

c.WhatsThisHelpID = prevDpi

~FormScaleForDpi(c, newdpi)

ElseIf TypeOf(c) Is TabStrip            ' Scale TabStrip's attached forms

For Each tb In c.Tabs

If TypeOf(tb.Ocx) Is Form

tb.Ocx.WhatsThisHelpID = prevDpi

~FormScaleForDpi(tb.Ocx, newdpi)

EndIf

Next

EndIf

EndIf

Next

.WhatsThisHelpID = newdpi     ' store new newdpi

EndWith

FormScaleForDpi = True

EndFunc

Remarks

By moving the form from one display to the other will show the results of the example. The example can only be tested if the program is run on or moved to a high-resolution display.

The form's _MessageProc event sub processes the WM_DPICHANGED message. When using two displays, of which one is a high-res screen, the message can be processed as documented when the high-res screen is to the right of the normal 96DPI screen. However, if the high-res screen is situated to the left of the 96DPI display the window movement to the high-res screen is messed up. Not only is the movement far from smooth, but the advised new dimension of the form can become corrupted. The result is unknown beforehand: the window might have the expected size or it is either too small or too large. To prevent the corruption the example introduces two static helper variables to help in a smooth movement and use the suggested size when done. The fInSizing boolean prevents the handling of WM_DPICHANGED if they are the result of SizeW. The CurDpi static variable prevents the handling if the processing and scaling for a given DPI is already done. To understand this we'll examine the situation where a window from a normal 96DPI screen is moved to a high-res screen at the left. Once the window is moved to the left screen and about 30% of the window is located at the left screen Windows sends a WM_DPICHANGED with the DPI for that screen (144 DPI, 192DPI). The default processing is to size - enlarge - the window to the new DPI. However, by enlarging it the major part of the window will return to the 96 DPI screen and a new WM_DPICHANGED with 96 dpi is sent to the window recursively. If the handler would call SizeW again the window would actually be enlarged and will be redrawn in that size. In the meanwhile, the window is being dragged to the left a new WM_DPICHANGED with 192 dpi is sent to inform that again 30% of the window is situated on the high-res screen. The WM_DPICHANGED handler will resize for 192 dpi and the whole cycle starts again. The fInSizing static variable prevents the recursive execution of the WM_DPICHANGED handler. The CurDpi variable holds the actual dpi (144, 192) the handler used to scale the window. If WM_DPICHANGED is finally called again with the same new dpi the handling is blocked, because the suggested size of the window might have become corrupted. To understand the use of these variables uncomment the Debug commands inside the WM_DPICHANGED handler.

There are two ways to make the GB program dpi-aware.

  1. Change the GfaWin32.exe (IDE) manifest to include the key. This is accomplished by saving or renaming the current manifest (GfaWin32.exe.manifest) and then rename the GfaWin32.exe.dpiawaremanifest to GfaWin32.exe.manifest. Then restart the IDE to execute the IDE as a dpi-aware application.
    When the IDE is executed on a high-resolution display with the dpi-aware manifest it will look very small (it is now dpi-aware). To develop dpi-aware software you will need two displays, a regular 96-dots-per-inch display to run the dpi-aware IDE in its native resolution and a high-res display to test the program.

  2. Use the DpiAwareness function to set the thread to dpi-awareness. The GfaWin32.exe's manifest needs not to be changed and the IDE is run in a 'normal' sized window; the IDE is not dpi-aware and is scaled by the Windows OS to run properly on a high-resolution display.

See Also

DpiAwareness, FontSize, ScaleToDpiSng, WinDpi, LoadFormPos, SaveFormPos, ScaleWidth, ScaleHeight, ScaleXYWHToDpi, WorkWidth, WorkHeight.

{Created by Sjouke Hamstra; Last updated: 03/03/2022 by James Gaite}