4

Microsoft can't figure out how to help me, so I have to ask here.

I have three monitors...

  • Screen 1: 3840 x 2160, scaling 150%
  • Screen 2: 1920 x 1200, scaling 100%
  • Screen 3: 1920 x 1200, scaling 100%

I need in VB.net or C# to get the scaling of each monitors.

Microsoft advises me to use this code:

Private Declare Function GetDeviceCaps Lib "gdi32.dll" (hdc As IntPtr, nIndex As Integer) As Integer

Public Enum DeviceCap
    VERTRES = 10
    DESKTOPVERTRES = 117
End Enum

Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    For Each screen As Forms.Screen In Forms.Screen.AllScreens
        Dim pnt = New System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1)
        Dim mon = MonitorFromPoint(pnt, 2)
        Dim fac As Single = GetScalingFactor(mon)
        Console.WriteLine($"Factor: {fac}")
    Next
End Sub

Private Function GetScalingFactor(monitorHandle As IntPtr) As Single
    Dim g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
    Dim desktop As IntPtr = g.GetHdc()
    Dim LogicalScreenHeight As Integer = GetDeviceCaps(desktop, CInt(DeviceCap.VERTRES))
    Dim PhysicalScreenHeight As Integer = GetDeviceCaps(desktop, CInt(DeviceCap.DESKTOPVERTRES))
    Dim ScreenScalingFactor As Single = CSng(PhysicalScreenHeight) / CSng(LogicalScreenHeight)
    Return ScreenScalingFactor
End Function

But it returns a scale of 1 for all my screens.

I need it to be independent of my app being dpiAware or not, so I have to read it from the screen control panel somehow.

The solution must work on both Windows 10 and Windows Server 2012 R2 Remote Desktop clients.

djv
  • 15,168
  • 7
  • 48
  • 72
MojoDK
  • 4,410
  • 10
  • 42
  • 80
  • [This](https://stackoverflow.com/questions/32607468/get-scale-of-screen) solution worked well for me. – João Silva Nov 21 '19 at 09:56
  • @JoãoSilva - I do not have a LogPixels entry in my registry. But thanks anyway. – MojoDK Nov 21 '19 at 10:05
  • [This](https://stackoverflow.com/q/5977445/21567) seems to do it - in various ways (albeit in C# but should be easy to convert). Pick your choice. – Christian.K Nov 21 '19 at 12:06
  • @Christian.K ... yeah this is one of the suggestions from Microsoft as well, but as many comments states, GetDeviceCaps always return 1 ... so we had to drop it. – MojoDK Nov 21 '19 at 19:53
  • 1
    Your code uses a WPF-style event handler. Is this a WPF app? WPF apps are DpiAware *by nature*, unless you forcibly disable it. Why don't you use the Matrix returned by `PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice;`? Using `Graphics.FromHwnd()` in WPF doesn't make any sense. – Jimi Nov 22 '19 at 03:50
  • @Jimi ... it has to work in Winforms, Wpf, dpiAware and non dpiAware ... so I need to read it from the system. – MojoDK Nov 22 '19 at 07:02
  • What is that you're building? Some sort of component/plug-in/library for other applications? If it's an application, you can just make it DpiAware. What's the problem with it? You should be more clear on the destination of this code. You could get make it work in Windows 10, using per-thread DpiAwareness, so you have one thread only that is DpiAware, generated in code. But you won't make it in Windows Server 2012. – Jimi Nov 22 '19 at 07:14
  • @Jimi ... it's 20 years of business code using both Winforms and Wpf. It's a mix of non high DPI and High DPI Pc's using remote desktop to access Windows Server 2012R2 rds. Our tests of DpiAware per montior (espcailly in Winforms) is not successfull, so we need to find the scale of each monitors and do the calculations ourself. – MojoDK Nov 22 '19 at 09:39
  • If you want to go down that road... See [QueryDisplayConfig](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-querydisplayconfig) and [GetScaleFactorForMonitor](https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getscalefactorformonitor?redirectedfrom=MSDN) (note the different platforms supported). Using `QueryDisplayConfig`, you're interested in the `SOURCE`. – Jimi Nov 22 '19 at 10:29
  • See also `HKEY_CURRENT_USER\Control Panel\Desktop\LogPixel` and read the notes here: [DPI-related APIs and registry settings](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-8.1-and-8/dn528846(v=win.10)) – Jimi Nov 22 '19 at 10:37

2 Answers2

0

I believe you can use the ResolutionScale property in the DisplayInformation class. This retrieves you an enum with the scale factor, but I do not know if it is dpiAware independent or not.

João Silva
  • 122
  • 10
0

This might help you.

Normally screen monitors have a rawDpi 102 dots x inch but if you have a Microsft Surface is logic you have another rawDpi. So in this case you can change 102 with real device rawDpi (you can get that by code under 100% screen resolution)

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    For Each screen In System.Windows.Forms.Screen.AllScreens

        Dim mRawSreen As SCREENS = Display(screen, DpiType.MDT_RAW_DPI)

        Console.WriteLine(" deviceName: " & screen.DeviceName &
                          " ScaleFactorX: " & (102 / mRawSreen.DPI_X * 100).ToString("N2") & "%" &
                          " ScaleFactorY: " & (102 / mRawSreen.DPI_Y * 100).ToString("N2") & "%")

    Next

End Sub

Public Enum DpiType
    MDT_EFFECTIVE_DPI = 0
    MDT_ANGULAR_DPI = 1
    MDT_RAW_DPI = 2
End Enum

Structure SCREENS
    Dim DPI_X As Integer
    Dim DPI_Y As Integer
End Structure

Private Function Display(screen As Screen, ByVal type As DpiType) As SCREENS

    Dim x, y As UInteger

    GetDpi(screen, type, x, y)

    Dim current As SCREENS = New SCREENS With {
            .DPI_X = x,
            .DPI_Y = y
            }

    'Console.WriteLine(" deviceName: " & screen.DeviceName & " typeDpi: " & type.ToString & " : dpiX=" & x & ", dpiY=" & y)
    Return current

End Function

Private Declare Function MonitorFromPoint Lib "User32.dll" (ByVal pt As System.Drawing.Point, ByVal dwFlags As UInteger) As IntPtr
Private Declare Function GetDpiForMonitor Lib "Shcore.dll" (ByVal hmonitor As IntPtr, ByVal dpiType As DpiType, ByRef dpiX As UInteger, ByRef dpiY As UInteger) As IntPtr

Sub GetDpi(ByVal screen As System.Windows.Forms.Screen, ByVal dpiType As DpiType, ByRef dpiX As UInteger, ByRef dpiY As UInteger)
    Dim pnt = New System.Drawing.Point(screen.Bounds.Left + 5, screen.Bounds.Top + 5)
    Dim mon = MonitorFromPoint(pnt, 2)
    GetDpiForMonitor(mon, dpiType, dpiX, dpiY)
End Sub
djv
  • 15,168
  • 7
  • 48
  • 72
G3nt_M3caj
  • 2,497
  • 1
  • 14
  • 16
  • Thanks for helping me out here .... but I'm not entirely sure, how the DPI code above will help me get the custom scaling, that the user has defined? – MojoDK Nov 22 '19 at 09:34
  • The raw DPI is different than the effective DPI. The effective DPI doesn’t change on screen custom resolution. Instead the raw DPI make changes in custom resolution. So at this point you can calculate by your self that, in percentage (as code above shows, normal raw DPI / custom raw DPI) To get the initial normal raw DPI you can get that also by this code and you can save on registry or where you want. That is to remove my static 102. :) – G3nt_M3caj Nov 22 '19 at 09:50