0

I have the following code to check for the DPI of a screen where a form is being displayed. It works correctly for regular (modeless) windows, but returns the DPI of the screen where the main window is displayed if the dialog is displayed modally. The apparent fault is with the call Process.GetCurrentProcess().MainWindowHandle, which seems to return the process ID of the main window (since presumably, the modal window runs under the calling window's process). Unfortunately, this causes layout issues when the DPI must be detected in the modal dialog and elements rearranged when it's dragged from one monitor to another.

How do I get the DPI of the window the modal dialog is being displayed on?

Thanks!

-Pete

Calling format:

Dim factor As Single = GetDpiWindowMonitor(Me) / 96.0

Code being called:

'Get DPI of monitor containing this window by GetDpiForMonitor.
Public Function GetDpiWindowMonitor(ByVal curForm As Form) As Single
    'Get handle to this window.

    Process.GetCurrentProcess().Refresh()

    Dim handleWindow As Integer = Process.GetCurrentProcess().MainWindowHandle
    ' ^^^ This seems to return the handle of the main window, not the dialog

    'Get handle to monitor.
    Dim handleMonitor As Integer = W32.MonitorFromWindow(handleWindow, W32.MONITOR_DEFAULTTONEAREST)

    'Get DPI.
    Dim curDPI As Integer = GetDpiSpecifiedMonitor(curForm, handleMonitor)
    Return curDPI
End Function



'Get DPI of a specified monitor by GetDpiForMonitor.
Public Function GetDpiSpecifiedMonitor(ByVal curForm As Form,
                                       ByVal handleMonitor As Integer) As Single


    'Check if GetDpiForMonitor function is available.
    If Not IsEightOneOrNewer() Then
        If curForm.CurrentAutoScaleDimensions.Width = 0 Then
            If FrmRegisteredTo.CurrentAutoScaleDimensions.Width > 0 Then
                Return FrmRegisteredTo.CurrentAutoScaleDimensions.Width
            Else
                Return 96
            End If
        Else
            Return curForm.CurrentAutoScaleDimensions.Width
        End If

    End If

    'Get DPI.
    Dim dpiX As UInteger
    Dim dpiY As UInteger

    Dim result As Integer = W32.GetDpiForMonitor(handleMonitor, W32.Monitor_DPI_Type.MDT_Default, dpiX, dpiY)

    If (result <> 0) Then   'If not S_OK (= 0)
        Throw New Exception("Failed to get DPI of monitor containing this window.")
    End If

    Return Convert.ToSingle(dpiX)
End Function
pbickford
  • 59
  • 1
  • 14
  • It's not clear why don't you pass the handle of the Modal Window instead of the handle of another Form. Also, as described [here](https://stackoverflow.com/a/53026765/7444103), you can use `[Screen].GetHashCode()` to get the handle of a Monitor (`hMonitor`). – Jimi Jun 04 '20 at 22:11
  • If you read the notes there, you'll get to [GetScaleFactorForMonitor](https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getscalefactorformonitor) which does the rest - if you need to currently selected scale factor - passing the aforementioned `hMonitor`. The current DPI is also returned by the current Device Context (also mentioned in the notes). – Jimi Jun 04 '20 at 22:20
  • If, for some reason, you need to know the DPI of the modal dialog in another Form, you can always make the modal dialog raise an event when it's first shown (to pass the information to the subscribers). The event handlers are called even when the Form that raises the event is shown as Modal. Otherwise, add some notes that describe when this information is need. – Jimi Jun 04 '20 at 22:54
  • I'm passing it the modal dialog, but it's returning the handle from he calling window. That said, is the proper direct way to get the DPI of the screen that the current form is on than (1) Getting the screen by calling Screen.FromControl(myForm), (2) Calling [Screen].GetHashCode() to get the handle of that screen, then (3) using GetScaleFactorForMonitor to get the DPI? – pbickford Jun 05 '20 at 17:42
  • Well, you're calling `GetDpiWindowMonitor(Me)`, but you didn't specify **when** this call is made. Then, in the method, you get `Process.GetCurrentProcess().MainWindowHandle`, which is completely useless here. Use the handle of the current Form to get the Screen where it's shown (**when** it's shown there) and derive `hMonitor` from that Screen. At this point, what results you want and what you can get depends on many factors: is your app DpiAware? Then the Form itself returns the correct information already: `Me.DeviceDpi` Otherwise it's a long, long story. – Jimi Jun 05 '20 at 18:03
  • The .Net version in use also counts, combined with the System version (in Windows 10 and .Net FW 4.7.1.+, you may have different results than what you get, in the same context and using the same tools, in, e.g., Windows 7). You may need (and probably want) to dig deeper. See, for example: [Detect if non DPI-aware application has been scaled/virtualized](https://stackoverflow.com/a/36864741/7444103). The details on your app's build (Framework version, DpiAwareness status, target Systems in your `app.manifest` file) are quite important and cannot be omitted. – Jimi Jun 05 '20 at 18:03
  • Also, follow the notes here: [Using SetWindowPos with multiple monitors](https://stackoverflow.com/a/53026765/7444103), it can give you some more info and tools to work with. The first part is really what matters the most, don't skip it. – Jimi Jun 05 '20 at 18:06
  • .Net Framework is 4.8, dpiawareness is set to in app.config. As a test, set one window up on screen 1 (144 dpi) and another on screen 2 (96 dpi). Both are returning me.devicedpi of 144. What gives? (BTW: REALLY appreciate your help on this!) – pbickford Jun 06 '20 at 23:35
  • Or you're reading from the same handle or when the Form is not actually already shown in this other Monitor. Have you tried to read the value in `OnShown`? Or event after, in a Button.Click or similar? Btw, disable `EnableWindowsFormsHighDpiAutoResizing` to test. – Jimi Jun 06 '20 at 23:41

0 Answers0