11

I am seeing an interesting difference between the resolution that is set through Control Panel and the output of Screen.Bounds for my widescreen laptop. The screen is 15.5" and the resolution set through Control Panel is 1920x1080. However when I run some code like this.

Screen[] screens = Screen.AllScreens;
foreach (Screen scr in screens)
{
    Console.WriteLine("Width: " + scr.Bounds.Width + ", Height: " + scr.Bounds.Width);
}

The output shows my resolution being 1536x864. I have done some looking around, and I thought it may be related to a DPI issue, and when I look at my display settings, the slider (I am on Windows 8.1) is in the middle, and the checkbox that states "Let me choose one scaling level for all my displays" is unchecked. I ran this little code to get the current DPI setting.

float dpiX, dpiY;
Graphics graphics = new System.Windows.Forms.Form().CreateGraphics();
Console.WriteLine("DPI: " + graphics.DpiX);

The DPI that is returned is 96.0 which by my understanding is the 100% DPI setting (so no enlargement or whatever it is called). What seems odd to me is that the bounds returned by Screen is exactly 80% of my actual resolution, which would make me think my DPI is set to 100 (or 125%) but it is not. I am only having this issue with my laptop screen, as my secondary monitor has bounds that are equal to resolution through Control Panel. Is this due to the fact that my DPI setting is not set to have the displays independent of eachother (that checkbox checked)? For a little bit of background, I am writing a tool that takes the current screens and gets pictures from reddit and fits them to the screens independently of each other, so whatever solution I have, it has to correctly get the resolution of each display.

Luaan
  • 62,244
  • 7
  • 97
  • 116
Isaac Levin
  • 2,809
  • 9
  • 49
  • 88
  • I presume `scr.Bounds..Primary` in your first code example should really be `scr.Bounds.Height`? – Steven Rands Jan 16 '15 at 16:00
  • If "Let me choose one scaling level for all my displays" is **unchecked** then surely you have different DPIs set for each screen? – Steven Rands Jan 16 '15 at 16:10
  • @StevenRands fixed the example and I would think that is the case, however if that box is unchecked, the DPI for both screens is still 96.0. Am I doing something wrong in the way I get the DPI? – Isaac Levin Jan 16 '15 at 16:18
  • Not wrong per se, just that the DPI for the `Graphics` instance probably defaults to that of the primary screen. Are you on Windows 8.1? There's a [GetDpiForMonitor](http://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx) API that might be useful. – Steven Rands Jan 16 '15 at 16:23
  • Interesting, can I call that from C# in .Net, or does it have to be in C++? – Isaac Levin Jan 16 '15 at 16:30
  • @IsaacLevin You can call it using a P/Invoke. There's a link to a tutorial for making a monitor DPI aware application in my answer (it's for WPF, but that should be fine). – Luaan Jan 16 '15 at 16:31

2 Answers2

15

I had same problem also for screen shot tool. I found solution and it works for me.

private enum ProcessDPIAwareness
{
  ProcessDPIUnaware = 0,
  ProcessSystemDPIAware = 1,
  ProcessPerMonitorDPIAware = 2
}

[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);

private static void SetDpiAwareness()
{
  try
  {
    if (Environment.OSVersion.Version.Major >= 6)
    {
      SetProcessDpiAwareness(ProcessDPIAwareness.ProcessPerMonitorDPIAware);
    }
  }
  catch (EntryPointNotFoundException)//this exception occures if OS does not implement this API, just ignore it.
  {
  }
}

You should call SetDpiAwareness() method before call functions to get system resolution, etc. Also if you have some UI in your application now it is your responsibility to scale your UI in screen with high DPI.

Hope this helps.

name1ess0ne
  • 868
  • 7
  • 17
  • Thanks! This is the only solution, that helped me with screen sharing program and scaling in Windows 8.1 – Art Jul 29 '15 at 06:47
  • 3
    One thing, on Windows 7 this code isn't necessary to scale the resolution, but if this code is run on Windows 7, try catch won't catch the error thrown and it'll crash the program. You'll need to change the error it's checking for, or in my case, I just set it to catch any exception. – Byte11 Nov 13 '17 at 22:14
  • This actually won't work for wpf applications. See the second answer on: https://stackoverflow.com/questions/23551112/how-can-i-set-the-dpiaware-property-in-a-windows-application-manifest-to-per-mo/ – Byte11 Jul 29 '18 at 00:08
4

I believe you have to notify the operating system that your application is DPI aware. Otherwise the OS pretends that everything is just fine, leading to the behaviour you're observing - the OS handles the resizing.

You can find some information about writing DPI aware applications here - http://msdn.microsoft.com/cs-cz/library/dd464646.aspx Of course, you should make sure your application actually is DPI aware - if not, you better stick with the default. It's not as nice, but at least it will work.

The main difference that Windows 8.1 brought to this is that you can have different DPI on different monitors, and you can query the monitor DPI API. .NET (and especially WPF) by default handles DPI awareness automatically, but only based on system DPI. If your monitors have different DPI settings, it will behave as non-DPI-aware (more precisely, system-DPI-aware, but the end result is your applications graphics are going to be virtualized by Windows). I'd expect that if you disconnect your second display, your application would behave as expected on your sole display (at least after manually setting the DPI, whatever the value).

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Thanks for this, it is helpful. Do you know if there is a way to get the DPI of both monitors at the same time? The example given works when moving the WPF App between monitors (120 on laptop, 96 on secondary) but I need to get the second monitor's DPI without moving the app over there. – Isaac Levin Jan 16 '15 at 17:38
  • @Issac To get DPI for each monitor, you need to call [GetDpiForMonitor](http://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx) function by P/Invoke. To get general overview of Per-Monitor DPI, check [Writing DPI-Aware Desktop and Win32 Applications](http://msdn.microsoft.com/en-us/library/windows/desktop/dn469266.aspx). – emoacht Jan 18 '15 at 01:32
  • @IsaacLevin Are you sure you need to do that? If you make sure your application is monitor-DPI aware, you'll get the correct screen bounds. Are you just trying to multiply the resolution by DPI or something? The form will actually have less space on the other display, as reported by `Screen.Bounds`. – Luaan Jan 19 '15 at 07:46
  • @Luaan, I am trying to get the bounds and DPI of both displays. Currently, what my process does is get both the monitors (by calling getting AllScreens member) then retrieving the bounds of each and manipulating an image to fit those bounds. The issue I am running into is that the resolution that is in bounds for my laptop display is wrong, and it is correct for my secondary display. I have set DPI aware, but the DPI is 96.0 for both. I am not sure what I am missing, but I know that I need one more step. – Isaac Levin Jan 19 '15 at 15:03
  • @IsaacLevin It still sounds like you're only system-DPI aware, not monitor-DPI aware. Have you verified this in Task Manager (or Resource manager, forgot which)? It should show OS compatibility (Windows 8.1) and DPI awareness (monitor-DPI aware) if you dig deep enough. – Luaan Jan 19 '15 at 15:16
  • It is DPI aware, I have verified that. My issue is that I can only seem to get the DPI of a monitor when the WPF application is on THAT monitor, which won't work for me. I need to be able to call something to get the DPI for both monitors (or better yet, the true resolution) without any user intervention. I hope this makes sense. – Isaac Levin Jan 19 '15 at 15:27
  • @IsaacLevin Oh, yeah - in that case, along with the DPI awareness, just call the `GetDpiForMonitor` as emoacht has linked in his comment. This allows you to get the DPI for any of the displays. – Luaan Jan 19 '15 at 16:29
  • That is where I am having a problem. Do I have to enumerate the displays then call GetDpiForMonitor for each? C++ is not my forte, so I would prefer to not have to write some helper method to get the DPI for the other displays in C++ – Isaac Levin Jan 19 '15 at 16:34
  • Yeah. But don't worry, you don't need any C++ - the WinAPI functions are all C-style, so they're easy to P/Invoke from C#. pinvoke.net is very handy for those, for example http://pinvoke.net/default.aspx/user32.EnumDisplayMonitors has a sample that gets all the monitors - the `hMonitor` parameter is the one you pass to `GetDpiForMonitor`. – Luaan Jan 19 '15 at 16:37
  • Awesome thanks!!! One more thing, does it make more sense to just get the DPI or the whole Monitor object at this point? – Isaac Levin Jan 19 '15 at 17:23
  • That really depends on what you want to use it for :) Querying just for the DPI is of course a lot less work, but if you need the other stuff, you can get it all. – Luaan Jan 20 '15 at 08:02