0

I'm developing a C# .NET 6.0 app to grab various information about the connected monitors. I can accurately get the number of pixels from WinAPI (EnumDisplayMonitors) even with scaling enabled but I cannot seem to find a way to get the actual monitor scale (as set in Windows System>display (The 100%/125%/150% setting.).

If I call the WinAPI call GetDpiForMonitor I get three different DPIs but nothing that corresponds to the 125%/150% I have set on my monitors. I have looked at the different values coming out of EnumDisplayMonitors and come sometimes see different sizes for dmPelsWidth but this doesn't seem to scale with the scale setting...

Any ideas - I've been playing with this on and off for a week and googled a lot. Figure there must be a simple way to get the 100%/125%/150% value from windows but all the answers I turn up give DPI values that do not seem to correlate to the setting (as set above).

Thanks.

cheesemp
  • 307
  • 1
  • 3
  • 10
  • Wasn't it that simple that 100% is always 96 DPI? – Ralf Jun 16 '23 at 10:43
  • Scale% = dpi * 100 / 96 – David Heffernan Jun 16 '23 at 10:47
  • I don't see this from GetDpiPerMonitor - I get Angular 78 at 175%, or its 110 for all other options. For raw its 90 at 175% or 126 for all other options. Effective is 96. Application is set as DpiAware (I had to fix this to get the correct monitor resolutions). – cheesemp Jun 16 '23 at 10:50
  • I'm using the ApplicationConfiguration.Initialize(); (See https://aka.ms/applicationconfiguration) which should be setting DPI awareness if you read the link. I've also got Windows 10 as a supportedOS in the manifest. – cheesemp Jun 16 '23 at 10:54
  • 1
    Setting Windows 10 as supported OS is not enough, try to set dpiAware & dpiAwareness values in the app.manifest https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process#setting-default-awareness-with-the-application-manifest – Simon Mourier Jun 16 '23 at 11:01
  • 2
    Did you add or edit the `` property in the main `PropertyGroup` of the Project's file? It should be set to `PerMonitorV2`, while the default is `SystemAware` -- `GetDpiForMonitor()` is not DpiAware, while `GetDpiForWindow()` is -- You should specify what kind of application you have, different built-in tools may be already available for this – Jimi Jun 16 '23 at 11:15
  • @SimonMourier - Those settings no longer work for .NET 6 (If I put them in the app won't start). See the link to ApplicationConfiguration. – cheesemp Jun 16 '23 at 11:23
  • @jimi - No I haven't set that. I will give it a try. My reading of the applicationconfiguration implied SystemAware was good enough. As for the app its just a simple .NET 6.0 Winforms app - just dumping the monitor info into a text box (Its just a simple test app at the moment to see if I can get the info I need). As I say I wanted to get each monitors setting so GetDpiForWindow wouldn't work. – cheesemp Jun 16 '23 at 11:26
  • 1
    A Window doesn't need to be exactly *visible* for its handle to be compatible with the `GetDpiForWindow()` call. Anyway, the screen scale is determined by the physical size divided by the logical size (of course, since it's proportional, just one dimension is enough :). You could use GetDeviceCaps then divide the value of, e.g., `DESKTOPVERTRES` with the value of `VERTRES` (I hope it's correct, also I don't remember the values, you'll have to check in `wingdi.h`). With a scale of `150%`, you' should get `1.5` -- Call to `GetHdc()` to get the hDC of a Desktop – Jimi Jun 16 '23 at 12:23
  • Or, just do what David Heffernan showed in his comment (you still need to move the Window of a DpiAware Thread or Process around) – Jimi Jun 16 '23 at 12:30
  • @jimi - will that work if screens are say vertically stacked (or even worse - 4 set in a square all with different resolutions?)? I'll certainly have a play. I'm just surprised how hard this info is to get considering its front and center of Windows 10/11 settings. – cheesemp Jun 16 '23 at 12:32
  • 1
    The *positions* of the screens affect the measures returned by VirtualScreen. Size and Font scale are a different thing -- This measure is usually not relevant for a Desktop application, which relies on non-virtualized DPI values. The scale is just the relation between the base value, `96`, and the current, notified when the screen parameters change – Jimi Jun 16 '23 at 12:36
  • Ok I will have a go. I may be misunderstand what DESKTOPVERTRES is referring to (I assumed it was total resolution of the desktop). I haven't played with GetDeviceCaps yet. If I can get it to work I'll leave an answer. Thanks for the help. – cheesemp Jun 16 '23 at 12:44
  • 1
    Of course they work, it's just lower level. Add this to the manifest and you'll see it works fine `PerMonitorV2` https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process like this https://pastebin.com/raw/wdKxqWW2 if the app doesn't work, it's another problem (xml error, etc.). Or if you want to stick to .NET way, use `SetHighDpiMode(HighDpiMode.PerMonitorV2)` or add this to the csproj `PerMonitorV2` – Simon Mourier Jun 16 '23 at 14:05
  • @SimonMourier - Ah thank you! I had the 2005 schema - the 2016 works (I must have copied from an older .NET Framework project). However GetDpiForMonitor() is now returning the same values for different DPI types no matter the scale set! I will have a play with Jimi's suggestions when I next get a chance to look at this. GetDpiForMonitor is obvious not much use. – cheesemp Jun 16 '23 at 16:06
  • 1
    Everything should work fine either with .NET 6 settings using Initialize or using the app manifest. I've tested it. – Simon Mourier Jun 16 '23 at 16:13
  • @SimonMourier - Looks like I was having a 'its Friday' moment (It didn't help I was going in and out of meetings) . Had the dpiAwareness but not the dpiAware tags in the manifest. With both in it works just as you said. I'm going to leave an answer for anyone else looking one. – cheesemp Jun 19 '23 at 09:01

1 Answers1

0

So following @SimonMourier and @Jimi's advice I've managed to solve this one. Just wanted to share since some of the info has changed since .NET Framework.

  1. Make sure you use ApplicationConfiguration.Initialize(); to initialize the Windows app correctly
  2. Set the following in the manifest (you need both):
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

You may also need:

 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
     <application>


  <!-- A list of the Windows versions that this application has been tested on
       and is designed to work with. Uncomment the appropriate elements
       and Windows will automatically select the most compatible environment. -->

  <!-- only Windows 10 and higher supported -->
  <!-- Windows 10 -->
  <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

</application>

I then used koopakiller's answer here: How to get DPI scale for all screens? (The public static class ScreenExtensions - GetDPI)

To get the Effective DPI for each screen (and dump into my displayInfo string):

foreach (var screen in System.Windows.Forms.Screen.AllScreens) { uint x, y; screen.GetDpi(DpiType.Effective, out x, out y); displayInfo += $" Scale {((double)x / 96)*100}%" + Environment.NewLine; }

I now get nice 125% etc - surprised this was this hard but just wanted to know what user had set for diagnostic purposes. Hope this helps someone and thanks once more to those who helped.

cheesemp
  • 307
  • 1
  • 3
  • 10