How can I update my WinForms app to behave better at high DPI, or at normal DPI on very large screens?


A customer had a WinForms app that was showing its age. It didn't scale itself properly on high-DPI screens, which means that if you ran it on a Surface Pro 4, you got a teeny-tiny window and then you had to go grab your magnifying glass in order to read it. The customer noted, "When the program is run on a large display, it occupies only a tiny portion of the screen. What is the least amount of work I need to do to get this program to look less awful on large screen? I am willing to accept blurriness in exchange for doing less work."

The lowest level of DPI awareness is "none", which is what you get if you do not manifest your application, you have a manifest but do not declare whether you are DPI aware, or if you have a manifest and you declare your DPI awareness as <dpiAware>False</dpiAware>. (The value is not case-sensitive.) For an application that is not DPI-aware, the operating system emulates a display that is 96 DPI: If the program asks for the monitor's DPI, it will be told that the monitor is 96 DPI. If the program asks for the resolution of the screen, the operating system adjusts the number so that the program sees a monitor that is filled with 96 DPI pixels. What actually happens is that the program's output is scaled so that each pixel is one 96th of an inch square. And the result is a little blurry because that's the nature of scaling up.

The next level of DPI awareness is "system DPI awareness", which is what you get if you manifest your application as <dpiAware>True</dpiAware>. For these applications, the operating reports the actual DPI of the highest-DPI monitor as the DPI of all monitors. If the application displays a window on the highest-DPI monitor, it is shown at actual size. If the application displays a window on any other monitor, it is scaled down as necessary so that each pixel is one Xth of an inch square, where X is the DPI of the highest-DPI monitor. (Since the highest DPI was reported to the app, the application's out will either be unscaled or scaled down. Scaling down results in less blurry results than scaling up, which is why the operating system reports the highest DPI of any monitor.)

The third level of DPI awareness is "per-monitor DPI awareness", which you opt into by setting <dpiAware>True/PM</dpiAware>. This strange not-really-a-Boolean value is a trick that takes advantage of the fact that versions of Windows prior to Windows 8.1 checked only that the value of dpiAware began with the letters t-r-u-e and ignored any trailing junk. As a result, a value of True/PM is interpreted as True on older systems, which means that an application that uses this manifest declaration gets per-monitor DPI on Windows 8.1 and higher, or system DPI on Windows 8 and lower.

Given the customer's willingness to accept blurriness in exchange for doing less work, the thing to do is to mark the program as DPI-unaware by setting <dpiAware>False</dpiAware> in the manifest, or leaving it out entirely.

The customer reported that they didn't have any such declaration in their application's manifest, but they were being treated as system DPI aware. Some investigation revealed that the very first line in its Main function is a call to the Set­Process­Dpi­Awareness function. That call is equivalent to setting the DPI awareness in a manifest, but you have to call it before you do anything that is dependent upon DPI.

Okay, so problem solved, right? Remove the call to Set­Process­Dpi­Awareness from the Main function, and now the program will be treated as DPI-unaware, and it will render at 96 DPI and be scaled up (blurrily) on higher-DPI monitors.

But of course that's not the entire problem. The customer explained that this got rid of the teeny-tiny text, but that wasn't the entire problem they had. "When users run the program on a machine with a very large screen, the window is properly scaled, but it is not taking advantage of the fact that the screen is a large 24-inch monitor. The program uses only about a third of the screen."

Okay, so the deal here is not that the program is rendering text that is too small to be read. The text is a perfectly readable size. The issue is that the developer wants the program to resize itself to something that covers more of the screen. That's not a DPI issue; that's just a program changing its default window size based on the screen dimensions. When your program starts up, check the dimensions of the monitor it is displayed on, do your calculations, and resize yourself if your calculations say that you want to be bigger.

Bonus reading:

Comments (38)
  1. Zarat says:

    What I don't get is why Microsoft didn't let it default to "not dpi aware, enable upscaling" unless the program actively opts in. So many programs don't handle high dpi but somehow end up not being scaled, including a lot of Microsofts own programs. Makes me hate working on high DPI machines.

    1. The MAZZTer says:

      According to Raymond's article here, that IS the default. The program was declaring itself as DPI aware (the developer probably did not like the blurriness and didn't have the visual resources to make it look nice at high-DPI).

      1. Zarat says:

        "The lowest level of DPI awareness is "none", which is what you get if you do not manifest your application,"

        I'm reading that as "once you have a manifest you have to opt-out", which is stupid, because these days most applications have a manifest.

        1. I updated the text to clarify. "none" is the default.

      2. Dave Bacher says:

        High DPI in 2009 is different from High DPI in 2016.

        12" panels running at 2160x1440 weren't a thing in 2009.

  2. SI says:

    Semi related: Is there a simple way of converting a size in pixels in a DPI unaware app into the pixel size on the native display? We needed to hack together a function that did this because the coordinates used by ::SHAppBarMessage are not scaled for DPI unaware apps, which means that not the entire area covered by the window is removed from the monitor's work area.

  3. Huw says:

    Customer to Microsoft - "Please code our entire program because we are too lazy"

    1. RKZENITH says:

      No, it just reminds me of how weird/wrong DPI awareness can be. You read 100 articles and all have their own special little tricks that kindof sortof work at the moment unless you do this or that and it explodes. It's too much like web design, especially that bit about only looking for t-r-u-e which is right along the same lines as various CSS selector tricks.

      Right after obscure metadata flags and manifests, the next biggest problem is the Visual Studio designer itself and its propensity to change or hide properties and then crash into a heap, expecting you to sort out whatever unwanted cruft it decided to choke on. I almost miss the way we used to have to set up GUIs in Visual C++ 6, as much of a mystery as some of that MFC/AFX macro syntax was at times.

  4. DWalker says:

    I have been planning to get a large, high DPI monitor, but even with Windows 10, some things don't look good yet (including some tiny dialog boxes that show up at installation, which is probably unavoidable during installation when the system isn't fully "there" yet). So I haven't.

    I wish monitors for sale weren't getting so "wide and short". My current 26-inch monitors are 1920 wide by 1200 tall; I had to search to find one that wasn't 1920 wide by only 1080 tall.

    I WANT my monitors to be 1920 wide by 1600 tall. :-) I have unused vertical space between the bottom of my monitors and the top of my desk, and that space could be filled with lines of code that I am working on!!!

    If I could find the right monitor. Sigh.

    1. Neyah says:

      There are also lots of monitors that are 1080 wide by 1920 tall.

    2. creaothceann says:

      @DWalker: Easy, just lower your monitor until it rests on the desk. :)

    3. Antonio Rodríguez says:

      Also, buy two ultra-wide (i.e., 2.35:1) monitors and place one on top of the other. Just kidding.

      I understand the consumer wants panoramic monitors for viewing films and TV, but they are terrible for web browsing and productivity applications (coding is, perhaps, one of the few exceptions when you realize that two code windows fit nicely side-by-side at 16:9). Why on Earth don't makers of "professional" equipment realize this and make 4:3 or 1:1 monitors?

      1. DWalker says:

        Right, I think that a 1:1 monitor would be fine for coding.

        I could set three 16:9 monitors side by side, turned vertically, but that's not ideal. a 1:1 ratio monitor would be ideal, at 1920 x 1920.

        1. Young says:

          See FlexScan EV2730Q; it's a 1920x1920 S-IPS panel, but they're shipped from Japan and about $1400.

          1. DWalker says:

            Nice! That's what I need. I have also found some other industrial monitors that are square; intended for medical applications like looking at X-rays and mammograms; and they are about $11,000 each.

      2. smf says:

        Actually I think panoramic monitors are better for your health. Up/down eye or head movement is more tiring than left/right.

    4. Alex Cohn says:

      Yes, the installer could be easily poisoned by some DPI-aware component, see https://blogs.msdn.microsoft.com/oldnewthing/20160617-00/?p=93695#comment-1253025

    5. smf says:

      @DWalker Buy a 2560 by 1600 monitor that doesn't stretch and then set a custom resolution. You can use the extra black lines on either side of the screen to store more post it notes.

      "I WANT my monitors to be 1920 wide by 1600 tall"

  5. George says:

    I noticed that Explorer has the setting explorer. I could not find any documentation about it. My guess is that it is a special setting that allows Explorer to turn on DPI awareness per shell extension. But in that case - HOW do you actually make a DPI aware shell extension, in particular one that is per-monitor aware?

  6. Antonio Rodríguez says:

    I wonder why Microsoft didn't add a new setting in Windows 8.1, instead of the queer True/PM. {{Cue True/False/FileNotFound joke here}}

    1. skSdnW says:

      They did, it is called "Per-monitor" but is rather useless since Vista/7/8.0 treats the app as DPI-unaware if you use that value.

      1. Wayne says:

        “True/PM”

        How does something like that get past code reviews? Why not just add another property that works in conjunction with instead of overloading a boolean property? It doesn't seem like this terrible hack is actually needed.

        1. SimonRev says:

          While neither agreeing or disagreeing with the design, I can see the logic:

          If you add a completely new state to the property (true / false / per-monitor), you break things on Windows 7.

          If you add a new property you potentially break things on Windows 7 (what if they set the new property to true, but omit the property, thereby letting it default to false?).

          At least this way every valid permutation is both forward and backward compatible. I suspect that is how it passed code review.

          1. SimonRev says:

            Bah, stupid forum engine removed my XML tag rather than escaping it. My post should have said:

            If you add a new property you potentially break things on Windows 7 (what if they set the new property to true, but omit the (dpiAware) property, thereby letting it default to false?).

          2. Dave Bacher says:

            Personally, I usually just test the first character -- and I usually default to true. Carry over from what they used to teach when I was growing up -- anything not false is true, and then why make whoever is editing type the entire word, they might have a audio cassette player as their only storage.

            Anyway -- the easy way to implement high DPI is to have each app render to a texture in the background, then map that to a quad. Your low DPI app just sets up the texture at the lower resolution.

            In that event, if you have an array with the hard coded DPI in the first element, the system DPI in the second element, and then the real resolutions past that, you can use just bitwise operators and a couple variables to return the correct DPI. If you used that approach, it'd be less costly than a compare and jump --but it'd become really important that you only set those variables if you were in high DPI mode, or else really stupid things would happen.

            And so in that case, you might want a single element -- that'd particularly be the case with XML if you allowed the elements to occur in any order in the file. I'd envision a dispatch table checking the element name and then calling some function to do the work -- and so if you allow the elements to occur in any order, you'd have issues setting up the mask directly. You'd have to set some variables and process them only after the entire manifest was parsed.

            That's a lot harder to verify as correct, too.

  7. Raymond, would you consider the implementation of checking dpiAware if it begins with t-r-u-e to be foresight, or dumb luck? Obviously in hindsight there are benefits (not that a second XML element/attribute is a huge impact), but just curious how the implementation came to be.

  8. Josh says:

    I had an old winforms app that was not dpi aware, yet it kept being rendered as if it was dpi aware. We set all the obvious switches to disable being dpi awareness, yet it still was being rendered tiny and dpi aware. It took us weeks to figure out that in one small corner of our app, we hosted a WPF control, and that control was telling the entire application that we were high dpi aware.

    The fix? In our assemblyinfo.cs file, add a line for
    [assembly: System.Windows.Media.DisableDpiAwareness]

  9. pete.d says:

    Very useful information.

    However, it also reminds me of Microsoft's annoying habit of fiddling with this every OS revision, and in particular the latest incarnation of DPI-handling, in Windows 10.

    Previously, you would just set the scaling of the fonts. This actually worked pretty well for most applications, as control sizes would scale up accordingly, and the text would still be rendered sharply. It did have the problem that some programs didn't provide enough wiggle room in the layout, so some text got truncated or otherwise hidden. But that came up rarely, and IMHO was less of an issue than *always* having to ready fuzzy text.

    Then the default became (in 7 or 8, I forget which) this new approach where the program was rendered at the nominal 96 dpi no matter what and then scaled up, producing a fuzzy image. This fixed the layout issue, but at the cost of visual quality. Fortunately, the display settings still included a check box to revert to the previous behavior, scaling just the fonts and not the entire window.

    Sadly, Windows 10 has removed this capability, for the most part. Yes, there's a way to set specific UI fonts individually, but it's a pain to use and is based on specific font sizes instead of percentages. And to effectively use that part of the UI, you have to set the nominal display scaling to 100%, making all the DPI-aware programs too small. There is a "compatibility tab" setting that will revert to the old behavior for specific programs, but that's a royal pain, having to track down every program you think you might use and which needs to be toggled off. I guess that wouldn't be so bad, except that the set of programs that need that setting toggled off includes a number of programs included with the OS (e.g. Device Manager).

    The not-DPI-aware experience sucks in some respect no matter what one does. But IMHO, visual quality has to take precedence. The default for not-DPI-aware programs should have been to scale the fonts and render at the real resolution. If an option needed to be provided, it should have been for the "render at 96dpi and scale up to the monitor" behavior, so users have to opt-in for fuzzy, hard-to-read text.

    1. smf says:

      "Previously, you would just set the scaling of the fonts. This actually worked pretty well for most applications, as control sizes would scale up accordingly"

      Most apps didn't handle it properly at all. It also doesn't work well for multiple monitors, when the monitors have different dpi. Most people gave up and either put up with small fonts, or bought a lower dpi monitor.

      The problem is still not fixed & I can still cope with 1920x1080. I would have got a 4k laptop a couple of months ago, but I don't think my eyes could have taken it.

      1. Zan Lynx' says:

        I've got a Dell m3800 with the 4K screen and I love it. However, there are many Java apps that don't do DPI. Number one of these is Eclipse.

        I was doing some hacking on an Intel Edison using their Eclipse-based IDE and I had to use the magnifier tool to read the icons.

        So, scaling just the text in an application is not really a good idea.

  10. Yuri Khan says:

    DPI is the wrong unit. Or, rather, misleading.

    A TV screen or, god forbid, a projector has very low DPI, so if you display actual honest-to-goodness 10pt (5/36 inch) text on it, there are not enough device pixels to make it legible. The system has to cope by lying about the DPI in order to present the user(s) with readable text.

    The important unit is device pixels per degree. An inch at one standard arm’s length is about two degrees. So, 24 DPI at 3 meters is roughly the same visual density as the ordinary monitor DPI at 70 cm.

  11. Antti Huovilainen says:

    There is an important special case where you can scale up without any blurriness: any integer ratio. Apple exploited this when introducing retina displays. IMO this is the only way to scale legacy apps that doesn't result in awful blurriness and headache due to squinting.

  12. Joshua says:

    In testing Windows Forms, I found that WIndows Forms code by default is high DPI enabled if you use the forms designer or place things in the constructor, and not high DPI enabled otherwise.

    There are two bugs, both of the same type. CheckedListBoxControl and CheckboxControl have a hardcoded 13 for checkbox size. Thankfully you can get at it easily and change it in a derived class. (13 * DPI + 48) / 96 works just fine.

    Incidentally, I found that almost all ancient programs do not support high DPI. I loaded the old-school smash all anti-aliasing settings to keep the blur from doing something dumb; however if you load a web browser control you will get an accessibility bug filed against you. (This recreates the really stupid accessibility bug that I had to make the jump to high DPI to fix.)

  13. Jolyon Direnko-Smith says:

    "True/PM" ... oh what a shame. Such a Golden Opportunity, missed.

    If Windows 8.0 only checks for "t-r-u-e" in the first 4 characters of this value, then it would have been possible to use a more completely accurate value to achieve the same end. Specifically:

    t-r-u-e-r

    This would even have left room for future improvements in DPI awareness being signalled with:

    t-r-u-e-r-s-t-i-l-l

    (Not being entirely serious, obviously. Or at all in fact) :)

  14. MarcK4096 says:

    I think a lot of programs set the DPI aware flag to true just to avoid blurriness back when 125% was the most that might be set. Now that 4K monitors are becoming more common, this bad decision is coming out of hiding.

    1. ender says:

      At 125% Vista, 7 and 8.x did not use blurry rendering (they still used the old-style scaling) - you had to go at least 126% to get it without editing the Registry directly. I had to do this at a client that wanted to use 125%, but had a proprietary program that refused to run at anything other than 96 DPI.

  15. Tihiy says:

    While you are it, raymond, can you tell when Win10 new DPI APIs will see the light? EnableNonClientDpiScaling, EnableChildWindowDpiMessage and new per-thread stuff?

Comments are closed.

Skip to main content