D2GetDpiRT and D2SetDpiRT commands

Requires: Direct2D.lg32

Purpose

Returns or sets the DPI for the specified render target.

Syntax

D2SetDpiRT RT, DpiX!, DpiY!
D2GetDpiRT RT, vDpiX!, vDpiY!

RT: object expression
DpiX, DpiY: float expression
vDpiX, vDpiY: float variables

Description

D2GetDpiRT returns the current DPI settings of the render target RT. Theoretically the x value and y value may differ, but the values are usually the same. For a dpi-unaware program (GFA-BASIC 32 and its EXEs) D2GetDpiRT will always return 96 dots-per-inch whether or not it is running on a high DPI display.

D2SetDpiRT is needed in dpi-aware programs. D2SetDpiRT sets the DPI for the specified render target. The DPI settings of the render target must match the DPI of the display. D2GetRT will set the render target's DPI setting when it creates a render target. For a dpi-unaware program the DPI will be set to 96 and the render target's DPI need not to be changed during the lifetime of the application. Even when the window of a dpi-unaware program is used on a high-resolution display. So, D2SetDpiRT won't be needed when the application is dpi-unaware (which is defined in the manifest file).

When the IDE and the EXEs are made dpi-aware (see Remarks) the application must respond to DPI changes and must adjust a render target's DPI setting for the new display DPI. Note that D2GetRT determines and uses the DPI for the window for which it creates the render target, but when the DPI changes or when the window is moved to another display the render target's DPI must be changed explicitly using D2SetDpiRT.

When a window is moved or when the DPI has changed, the windows of a dpi-aware application is notified through the WM_DPICHANGED message. The message should be handled in the _MessageProc event sub. A proper response is to adjust the window size, the render target's DPI, and also the render target's size (because the window size has just been adjusted).

Example

'

' D2SetDpi sample

' Shows how to handle a dpi-aware app used

' on a multiple monitor system with a high-resolution display.

'

$Library "Direct2D"

$Library "gfawinx"

Global DpiContext% = DpiAwareness()

OpenW 1, 0, 0, 0, 0, ~15 ' Create a window

Dim m$() : Array m$() = "File" #10 "Dummy" #10

Menu m$()                ' Add a dummy menu

FormScaleForDpi(Me)

Global Object RT1

Set RT1 = D2GetRT()

' D2GetRT automatically detects and uses the DPI for the window.

' Reason: the render target's DPI must match the display's DPI,

' otherwise the GetSize function returns wrong values.

' For the same reason the DPI of the rendertarget must be

' adjusted explicitly when the DPI changes.

Do

Sleep

Until Me Is Nothing

 

Sub Win_1_Paint

Local Float w, h

D2BeginDraw RT1, D2C_Aquamarine

' Note - For a dpi-unaware program the size in

'  DIPs and pixels are equal.

D2GetSizeRT RT1, w, h

D2Text 0, 0, "Width in DIPs: " + Str(w)

D2Text 0, 16, "Height in DIPs: " + Str(h)

D2GetSizeRT RT1, w, h, True

D2Text 0, 32, "Width in pixels: " + Str(w)

D2Text 0, 48, "Height in pixels: " + Str(h)

D2EndDraw

EndSub

 

Sub Win_1_ReSize

D2ResizeRT RT1, _X, _Y           ' size from adjusted window

EndSub

 

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

' WM_DPICHANGED is sent in dpi-aware apps after a DPI change

' or a window movement to another display with another DPI.

' The sizes of window and rendertarget must be changed accordingly.

If Mess == WM_DPICHANGED

If FormScaleForDpi(Me, wParam%, lParam%)

D2SetDpiRT RT1, LoWord(wParam%)    ' Set DPI of the RT of Win1

EndIf

retval = 0, ValidRet? = True

EndIf

EndSub

 

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

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

The example shows how to handle DPI changes for a DPI-aware (GB) program. To see the example in action you'll need two displays, one of them must be a high-resolution display.

To make GB dpi-aware the GfaWin32's manifest file has to include a dpiAware key so that the IDE starts as a dpi-aware program. For this purpose there is a GfaWin32.exe.dpimanifest file in the Bin directory that needs to be activated. First save the original manifest and then rename the dpimanifest to GfaWin32.exe.manifest. The manifest ensures the application will be per monitor DPI-aware (requires Window 10). After renaming the manifest restart GFA-BASIC, the IDE will now be dpi-aware. The result might not be pretty depending on the display GFA-BASIC is running. On a high-resolution display, the toolbar and the fontsize are too small and the Form-editor is practically unusable. However, when a dpi-aware program is run on 96 dots-per-inch regular display it renders normally. To develop a dpi-aware application you best use GB's IDE on the 96 DPI display and run the g32 program on the high-resolution display.

See D2GetSizeRT for more information on DPI and DIP.

See D2PixelsToDIP for information on handling mouse input on multiple monitors.

See Also

D2GetRT, D2GetSizeRT, D2AdjustW

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