none
Display geometry reading error with 125pc or 150pc text settings RRS feed

  • Question

  • I have a screen-grab system that uses standard VB screen measurements to return the X-Y and width-height of the rectangular area set by the user each time. This works fine unless 125% or 150% text size is set, when the measurements are wrong, presumably by the set percentage from the screen 0,0 location. How can I determine the text size the user has set prior to firing the screen-grab code so that I can apply the percentage change necessary to the returned X-Y and width-height numbers and thereby copy the correct rectangle on the screen?

    Sunday, September 3, 2017 2:05 PM

All replies

  • To get the text scaling, it can be done with GetDeviceCaps() or GetDpiForSystem() (with dpiAware set to true)

    but it doesn't work without sign out/sign in if it has been changed in Display Settings (tested on Windows 10)

    Declarations :

        Public Const LOGPIXELSX = 88
        Public Const LOGPIXELSY = 90
    
        <DllImport("Gdi32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)>
        Public Shared Function GetDeviceCaps(hDC As IntPtr, nIndex As Integer) As Integer
        End Function
    
        <DllImport("User32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)>
        Public Shared Function GetDpiForSystem() As UInteger
        End Function

    Sunday, September 3, 2017 3:51 PM
  •  Without knowing how you get the Rectangle,  there are two options i can think of.  The first is to enable the <dpiAware> setting in the application manifest file.  This will make the app return the correct DpiAware scaled values for things like a Form or Control Bounds property or Location Property.  It also fixes the rectangle bounds/sizes of the screens obtained using the Screen class too.  Not sure if these are the methods you use to get your rectangle from though.

      To change it in your app.manifest,  go to your Project Properties,  Application tab.  Click the (View Windows Settings) button to open the (app.manifest).  Scroll down through and find the below code...

      <!--
      <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
      -->
     



     You will need to un-comment this code by removing the "<!--" and "-->" from above and below the xml,  like this...

      <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
     

     Enabling the DpiAware setting is the way i recommend using if possible.  Not only will it fix the things i mentioned already but,  it also makes the text in labels and other controls look much cleaner,  not fuzzy looking.

     

     On the other hand,  if you are not using methods as mentioned above,  then you can get the X and Y scale that the screen is using as shown below with the GetDeviceCaps api function.  The ScreenScale's X and Y can be used to scale your rectangle.  Try this example with (150%) and without using scaling (100%).  Requires 1 Button and 2 Labels on the form.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        <DllImport("gdi32.dll")> Private Shared Function GetDeviceCaps(ByVal hdc As IntPtr, ByVal nIndex As Integer) As Integer
        End Function
    
        Private Const DESKTOPVERTRES As Integer = &H75
        Private Const DESKTOPHORZRES As Integer = &H76
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            'Gets the (unscaled) bounding rectange of this form and shows it in label1
            Dim br As Rectangle = Me.Bounds
            Label1.Text = "DpiAware (False): " & br.ToString
    
            'Gets the horizontal and vertical screen scale as a PointF structure. (X=Horizontal Scale) (Y=Vertical Scale)
            Dim ScreenScale As PointF = PointF.Empty
            Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
                Dim hDc As IntPtr = g.GetHdc
                ScreenScale.X = CSng(Math.Round(GetDeviceCaps(hDc, DESKTOPHORZRES) / Screen.PrimaryScreen.Bounds.Width, 2))
                ScreenScale.Y = CSng(Math.Round(GetDeviceCaps(hDc, DESKTOPVERTRES) / Screen.PrimaryScreen.Bounds.Height, 2))
                g.ReleaseHdc(hDc)
            End Using
    
            'Creates a new (scaled) rectangle from the (unscaled) bounding rectangle
            Dim brScaled As New Rectangle(CInt(br.X * ScreenScale.X), CInt(br.Y * ScreenScale.Y), CInt(br.Width * ScreenScale.X), CInt(br.Height * ScreenScale.Y))
    
            Label2.Text = "DpiAware (True): " & brScaled.ToString
        End Sub
    End Class



    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Sunday, September 3, 2017 5:54 PM Fixed Rounding Error In Code
    • Marked as answer by NickBuck Monday, September 4, 2017 10:13 AM
    • Unmarked as answer by NickBuck Monday, September 4, 2017 10:18 AM
    Sunday, September 3, 2017 4:16 PM
  • That all looks very interesting - thanks to you both for the detailed responses. I'll work through both these options at the next opportunity.

    Nick B

    Monday, September 4, 2017 10:13 AM
  • Me again,

    Should have looked first ... my app.manifest doesn't have the -

      <application xmlns="urn:schemas-microsoft-com:asm.v3">

    line that you refer to. My application was started in 2006 and I doubt I've looked at the manifest since then. It has only the copy below, so presumably I can insert the xml you suggest in the compatibility section?

    Nick B

    <?xml version="1.0" encoding="utf-8"?>
    <asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
          <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
            <!-- UAC Manifest Options
                If you want to change the Windows User Account Control level replace the 
                requestedExecutionLevel node with one of the following.

            <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
            <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
            <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

                Specifying requestedExecutionLevel node will disable file and registry virtualization.
                If you want to utilize File and Registry Virtualization for backward 
                compatibility then delete the requestedExecutionLevel node.
            -->
            <requestedExecutionLevel level="asInvoker" uiAccess="false" />
          </requestedPrivileges>
          <applicationRequestMinimum>
            <PermissionSet Unrestricted="true" ID="Custom" SameSite="site" />
            <defaultAssemblyRequest permissionSetReference="Custom" />
          </applicationRequestMinimum>
        </security>
      </trustInfo>
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
          <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->
          <!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
          <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
        </application>
      </compatibility>
      <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
      <!-- <dependency>
        <dependentAssembly>
          <assemblyIdentity
              type="win32"
              name="Microsoft.Windows.Common-Controls"
              version="6.0.0.0"
              processorArchitecture="*"
              publicKeyToken="6595b64144ccf1df"
              language="*"
            />
        </dependentAssembly>
      </dependency>-->
    </asmv1:assembly>

    Monday, September 4, 2017 10:25 AM
  • Me again,

    Should have looked first ... my app.manifest doesn't have the -

      <application xmlns="urn:schemas-microsoft-com:asm.v3">

    line that you refer to. My application was started in 2006 and I doubt I've looked at the manifest since then. It has only the copy below, so presumably I can insert the xml you suggest in the compatibility section?

    Nick B

    <?xml version="1.0" encoding="utf-8"?>
    <asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
          <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
            <!-- UAC Manifest Options
                If you want to change the Windows User Account Control level replace the 
                requestedExecutionLevel node with one of the following.

            <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
            <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
            <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

                Specifying requestedExecutionLevel node will disable file and registry virtualization.
                If you want to utilize File and Registry Virtualization for backward 
                compatibility then delete the requestedExecutionLevel node.
            -->
            <requestedExecutionLevel level="asInvoker" uiAccess="false" />
          </requestedPrivileges>
          <applicationRequestMinimum>
            <PermissionSet Unrestricted="true" ID="Custom" SameSite="site" />
            <defaultAssemblyRequest permissionSetReference="Custom" />
          </applicationRequestMinimum>
        </security>
      </trustInfo>
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
          <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->
          <!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
          <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
        </application>
      </compatibility>
      <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
      <!-- <dependency>
        <dependentAssembly>
          <assemblyIdentity
              type="win32"
              name="Microsoft.Windows.Common-Controls"
              version="6.0.0.0"
              processorArchitecture="*"
              publicKeyToken="6595b64144ccf1df"
              language="*"
            />
        </dependentAssembly>
      </dependency>-->
    </asmv1:assembly>

     If you have used at least VS2015 to open and upgrade your project,  i believe it should work if you add the xml to your app.manifest.  To test it,  i just made a project with VS2010 in which it did not seem to help adding it.  However,  when i upgraded that 2010 project in VS2015 and then added the xml to the app.manifest,  it worked.

     The xml can be placed basically anywhere between the other child nodes of the <assembly> root node.  However, here is the full app.manifest from the upgraded project so you can see where i inserted the xml.  It is after the <compatibility> node but,  before the <dependency> node.  This is where it is placed by default in a new form project's app.manifest file in VS2015 community.

    <?xml version="1.0" encoding="utf-8"?>
    <asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
          <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
            <!-- UAC Manifest Options
                If you want to change the Windows User Account Control level replace the 
                requestedExecutionLevel node with one of the following.
    
            <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
            <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
            <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
    
                Specifying requestedExecutionLevel node will disable file and registry virtualization.
                If you want to utilize File and Registry Virtualization for backward 
                compatibility then delete the requestedExecutionLevel node.
            -->
            <requestedExecutionLevel level="asInvoker" uiAccess="false" />
          </requestedPrivileges>
        </security>
      </trustInfo>
      
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
          <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->
    
          <!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
          <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
          
        </application>
      </compatibility>
    
      <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
           DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
           to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
           also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
    
      <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
    
      <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
      <!-- <dependency>
        <dependentAssembly>
          <assemblyIdentity
              type="win32"
              name="Microsoft.Windows.Common-Controls"
              version="6.0.0.0"
              processorArchitecture="*"
              publicKeyToken="6595b64144ccf1df"
              language="*"
            />
        </dependentAssembly>
      </dependency>-->
    
    </asmv1:assembly>

     

     

     

    If you say it can`t be done then i`ll try it

    Tuesday, September 5, 2017 5:02 PM
  • Nice Razerz!

    La vida loca

    Tuesday, September 5, 2017 8:41 PM