DPI Awareness - Unaware in one Release, System Aware in the Other [duplicate]
Asked Answered
B

1

20

So we have this really odd issue. Our application is a C#/WinForms app. In our 6.0 release, our application is not DPI aware. In our 6.1 release it has suddenly become DPI aware.
In the 6.0 release, if you run it in high DPI, it uses Windows bitmap scaling, which is fine, because that does not affect the screen layouts. In the 6.1 release, because it has for some reason become DPI aware, the user interfaces get mangled.
We are not in a position to fix this right now. We have hundreds of screens, so making them all work properly in DPI aware mode will take a lot of time.

We have confirmed this using SysInternals Process Explorer. In our 6.0 release, it shows Unaware, but in our 6.1 release, it initially shows Unaware, but then changes to System Aware.
The latter occurs when the code enters from the EXE into our assembly DLL that contains all of our user interface code (our EXE is basically a very thin shell; all it really does is call a Controller class on our presentation layer assembly.)

We have confirmed the following:

  • Both versions are built in Release Mode using VSS 2017.
  • Both versions target the same .NET Framework (4.5)
  • Both versions use the same DevExpress version.
  • Both versions have the same application manifest, which does not have the DPI awareness setting enabled.
  • Neither versions have any calls to any DPI related Windows APIs.
  • Using Sys Internals and some message boxes we determined at what point the 6.1 release becomes aware (entry point into Presentation assembly) and what DLLs are loaded at that point (ours, DevExpress, other dependencies), and then we built a small dummy app that references the same DLLs, and confirmed that these are loaded. That dummy app does not become DPI aware.
  • We have compared the main csproj files between both releases and there are no meaningful differences.
    • Neither release references anything from WPF.

We don't understand why our 6.1 release has suddenly become DPI aware. We are clueless what else to look at and we need a fix that puts this release back to DPI unaware mode. It's holding up our release. Would really appreciate any pointers. We are willing to try anything at this point.

Breakdown answered 8/5, 2018 at 17:23 Comment(7)
Are you referencing something ("Presentation") that comes from WPF? Something has changed from 6.0 to 6.1. You have mentioned what hasn't changed. Also, see SetProcessDpiAwareness to force it.Marven
Updated original post to indicate nothing from WPF is referenced. We don't want the application to be DPI aware. We want it to use Window's automatic bitmap scaling. Because making the application work in DPI aware mode means updating lots of user interfaces to behave properly in DPI aware mode.Breakdown
the manifest does not have the DPI awareness. But does it disable it explicitly: <dpiAware>False</dpiAware>? Refer to Hans answer for this. DPI Awareness is thread based. Read the MSDN document I linked, move to SetThreadDpiAwarenessContext if necessary. A dependency can causes this (referencing a WPF assembly). Telerik controls that use DPL do.Marven
... If that's the case (and you're unaware of it), try setting this in your assemblyinfo.cs file, [assembly: System.Windows.Media.DisableDpiAwareness].Marven
@Jimi: we did that, and that fixed it. No idea why it happened, as we have the exact same dependencies for both releases. We painstakingly verified that. If you want to post your comment as the answer, then I will up-vote it for you. Thanks!Breakdown
Well, I'm glad it helped. It's holding up our release seemed troubling. I will post an answer as soon as I have some time to write it down.Marven
Another busybody marks a question as duplicate that isn't. I would never have searched for the subject of the supposedly duplicated question. This question is relevant and was very well presented.Mancuso
M
19

About the problem reported in this Question:
An application, which is DPI-unaware by design, relying on Windows virtualization to scale its UI content, suddenly (although after some modifications, leading to a minor release update) - and apparently without an observable reason - becomes DPI-Aware (System Aware).

  • The application also relies on an interpretation of the app.manifest <windowsSettings>, where the absence of a DPI-awareness definition, defaults (for backward compatibility) to DPI-Unaware.

  • There are no direct references to WPF assemblies and no DPI-related API calls.

  • The application includes third-party components (and, possibly, external dependencies).


Since DPI-Awareness has become a relevant aspect of UI presentation, given the diversity of screens resolutions available (and related DPI scaling settings), most component producers have adapted to High-DPI and their products are DPI-Aware (scale when a DPI change is detected) and make use of DPI-Aware assemblies (often referencing WPF assemblies, DPI-Aware by definition).

When one of these DPI-Aware components is referenced in a project (directly or indirectly), a DPI-Unaware application will become DPI-Aware, when DPI-Awareness has not been disabled explicitly.

The more direct (and recommended) method to declare an assembly DPI-Awareness, is to declare it explicitly in the application manifest.

Refer to Hans Passant's answer for an application manifest setting prior to Visual Studio 2017:
How to configure an app to run on a machine with a high DPI setting

Since Visual Studio 2015-Upd.1, this setting is already present in app.manifest, it just needs to be uncommented. Set the section: <dpiAware>false</dpiAware>.

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

  //(...)
   
  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
       DPI. 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">false</dpiAware>
    </windowsSettings>
  </application>

//(...)

</assembly>

Refer to these MSDN articles for more information:
High DPI desktop application development on Windows
Setting the default DPI awareness for a process

Another method is to set the process context DPI-Awareness using these Windows API functions:

Windows 7
SetProcessDPIAware

[DllImport("user32.dll", SetLastError=true)]
static extern bool SetProcessDPIAware();

Windows 8.1
SetProcessDpiAwareness

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

enum ProcessDPIAwareness
{
    DPI_Unaware = 0,
    System_DPI_Aware = 1,
    Per_Monitor_DPI_Aware = 2
}

Windows 10, version 1703
SetProcessDpiAwarenessContext()
(When opting for a Per-Monitor DPI-Awareness, use Context_PerMonitorAwareV2)

Also see: Mixed-Mode DPI Scaling and DPI-aware APIs - MSDN

Windows 10, version 1809 (October 2018)
A new DPI_AWARENESS_CONTEXT has been added: DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED

DPI unaware with improved quality of GDI-based content. This mode behaves similarly to DPI_AWARENESS_CONTEXT_UNAWARE, but also enables the system to automatically improve the rendering quality of text and other GDI-based primitives when the window is displayed on a high-DPI monitor.

Use the GetWindowDpiAwarenessContext() function to retrieve the DPI_AWARENESS_CONTEXT handle of a Window and GetThreadDpiAwarenessContext() for the DPI_AWARENESS_CONTEXT handle of the current thread. Then GetAwarenessFromDpiAwarenessContext() to retrive the DPI_AWARENESS value from the DPI_AWARENESS_CONTEXT structure.

[DllImport("user32.dll", SetLastError=true)]
static extern IntPtr GetWindowDpiAwarenessContext(IntPtr hWnd);

[DllImport("user32.dll", SetLastError=true)]
static extern IntPtr GetThreadDpiAwarenessContext();

[DllImport("user32.dll", SetLastError=true)]
static extern int GetAwarenessFromDpiAwarenessContext(IntPtr DPI_AWARENESS_CONTEXT);


[DllImport("user32.dll", SetLastError=true)]
static extern int SetProcessDpiAwarenessContext(DpiAwarenessContext value);

// Virtual enumeration: DPI_AWARENESS_CONTEXT is *contextual*. 
// This value is returned by GetWindowDpiAwarenessContext() or GetThreadDpiAwarenessContext()
// and finalized by GetAwarenessFromDpiAwarenessContext(). See the Docs.

enum DpiAwarenessContext
{
    Context_Undefined = 0,
    Context_Unaware = (DPI_AWARENESS_CONTEXT) -1,
    Context_SystemAware = (DPI_AWARENESS_CONTEXT) -2,
    Context_PerMonitorAware = (DPI_AWARENESS_CONTEXT) -3,
    Context_PerMonitorAwareV2 = (DPI_AWARENESS_CONTEXT) -4,
    Context_UnawareGdiScaled = (DPI_AWARENESS_CONTEXT) -5
}

Since DPI-Awareness is thread-based, these settings can be applied to a specific thread. This can be useful when redesigning a user interface to implement DPI-Awareness, to let the System scale a less important component while focusing on the more important functionalities.

SetThreadDpiAwarenessContext
(Same parameter as SetProcessDpiAwarenessContext())

Assemblyinfo.cs
If a third-party/external component, which references WPF assemblies, redefines the DPI-Awareness status of an application, this automatic behavior can be disabled, inserting a parameter in the Project's Assemblyinfo.cs:

[assembly: System.Windows.Media.DisableDpiAwareness]
Marven answered 10/5, 2018 at 15:45 Comment(8)
your ContextDPIAwareness Enum values differs from these: https://mcmap.net/q/664094/-wpf-clickonce-dpi-awareness-per-monitor-v2 could you confirm which are the correct values?.Exalted
@Exalted DPI_AWARENESS_CONTEXT. I need to update this because the October 2018 (1809) update of Windows 10 has added a new value: DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALEDMarven
@Exalted Updated with some more informations. It should be more clear now.Marven
Yes I seen it in the Microsoft docs, and thanks for updating it but I didn't mean that, see, your Enum values originally were: -1,-2,-3,-4, and the values of the other user are: 16,17,18,34. That was confusing, one is sharing the wrong values. Also in your update it seem that you are missing to share the value of a constant: DPI_AWARENESS_CONTEXT. I just asked which values are correct, yours, or the other user, to avoid download the Windows SDK and check for the correct values in the headers. But excuse me if I bothered you in some way with that question, i just asked to avoid doing that.Exalted
@Exalted DPI_AWARENESS_CONTEXT (see the Docs and what I've written) is not a constant. It's a handle to an opaque structure, returned by the two described functions: GetWindowDpiAwarenessContext and GetThreadDpiAwarenessContext. An actual value is then retrived using GetAwarenessFromDpiAwarenessContext, which will give back a converted DPI_AWARENESS_CONTEXT, representing the value. You need to subtract the ContextDPIAwareness enumerator value and pass that value to SetProcessDpiAwarenessContext or SetThreadDpiAwarenessContext to set the DPIAwareness context.Marven
@Exalted Hardcoding some arbitrary values may work or not, depending on the Windows version and Update. The actual value may change, that's way it's provided in this contorted :) manner. Btw, you're not bothering me in any way. Your questions are on point and, I'ld add, quite interesting for what I've seen in the past.Marven
Then, in other words, the ContextDPIAwareness Enum in your code is symbolic code, is not a true P/Invoke implementation, I'm right?, since you can't define the value of (DPI_AWARENESS_CONTEXT) at design time. But then I see another user sharing another different Enum like this: https://mcmap.net/q/664095/-c-pinvoke-for-getwindowdpiawarenesscontext. Just I was confused by all these distinct Enum values, sorry. It's clear now, thanks.Exalted
@Exalted Well, yes, DPI_AWARENESS_CONTEXT can't be set at design-time because it doesn't exist at that stage. You need to derived it (and it may change from Process to Thread awareness). The enumerator is used in combination with the transformed value that GetAwarenessFromDpiAwarenessContext returns. Look, when I'll have Visual Studio at hand I'll post a sample code for this. Since you were right, it wasn't that clear; I assumed that someone, reading this, would have gone to the Docs and got it right automagically. But a C# implementation is actually mssing in PInvoke.net, too.Marven

© 2022 - 2024 — McMap. All rights reserved.