Dpi Awareness - Unaware in One Release, System Aware in the Other

Process unexpectedly DPI aware

My service is running as SYSTEM. When I call CreateProcessAsUser, in this case, the executable is also run as SYSTEM. I pass nullptr for the lpEnvironment parameter. MSDN says this:

A pointer to an environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process.

However, when I inspect the environment of my executable, I see:

__COMPAT_LAYER=HighDpiAware

This is forcing per-monitor DPI awareness. This is mysterious because, indeed - the AppCompatFlag is set for that executable in the registry for S-1-5-18 (SYSTEM), but I don't know how or where this value is coming from.

The variable is not set on my service (which also runs as SYSTEM) - presumably services don't get the AppCompat environment? But why does my child process have it, despite supposedly inheriting the environment of it's parent? I suppose these compatability flags must have special handling.

Anyway, the answer to my question is: remove HighDpiAware from the __COMPAT_LAYER environment variable.

WPF ClickOnce DPI awareness Per-Monitor v2

First of all, anyone who want's to cry out - simply target .NET 4.6.2, per monitor DPI awareness is enabled by default - that is simply not true.

What is enabled by default in .NET 4.6.2 is the boilerplate code behind the scenes - quite nasty c++ hooks in window declaration code to enable support for per-monitor dpi awareness.

You still have to declare that you support per monitor dpi awareness via an app.manifest, but ClickOnce does not support it.

(do note that earlier versions of .NET will not support per monitor dpi awareness, even with the app manifest, unless you manually add the boilerplate code)

Now to the answer:

  1. Make sure your project targets .NET 4.6.2. (easisest to use Visual Studio 2017 for this)
  2. Disable DPI awareness in the assembly properties. Trust me on this, we shall re-enable it later in code. To do this, open AssemblyInfo.cs under your project Properties node (expand the spanner icon inside the SolutionExplorer, usually on the right side). Add the following code to the very last line: [assembly: DisableDpiAwareness]. (this will require a using System.Windows.Media; statement, simply click the light bulb that appears when hovering over the red squiggly line and add the suggested using statement)
  3. Add an app.manifest file and declare support for Win 10 and other OS versions. Right-click your project in SolutionExplorer -> add -> new item -> Application manifest file. Open the created manifest file and in the middle there is a section with OS versions, uncomment them as such:
    OS versions in app.manifest
    Do not uncomment the section about DPI awareness (a bit further down)! ClickOnce will throw errors if you do.
  4. You will now need the following c# code somewhere in your project, I recommend creating a new static class for this:

    internal static class NativeMethods
    {
    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag);

    [DllImport("SHCore.dll", SetLastError = true)]
    internal static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);

    [DllImport("user32.dll")]
    internal static extern bool SetProcessDPIAware();

    internal enum PROCESS_DPI_AWARENESS
    {
    Process_DPI_Unaware = 0,
    Process_System_DPI_Aware = 1,
    Process_Per_Monitor_DPI_Aware = 2
    }

    internal enum DPI_AWARENESS_CONTEXT
    {
    DPI_AWARENESS_CONTEXT_UNAWARE = 16,
    DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
    DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
    DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
    }
    }
  5. The final part is calling the above p/invoke methods and declaring dpi awareness support. We need to do this before any other code runs. For this, right-click app.xaml in SolutionExplorer and select View Code. Then add this code:

    protected override void OnStartup(StartupEventArgs e)
    {
    if (Environment.OSVersion.Version >= new Version(6, 3, 0)) // win 8.1 added support for per monitor dpi
    {
    if (Environment.OSVersion.Version >= new Version(10, 0, 15063)) // win 10 creators update added support for per monitor v2
    {
    NativeMethods.SetProcessDpiAwarenessContext((int)NativeMethods.DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
    }
    else NativeMethods.SetProcessDpiAwareness(NativeMethods.PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
    }
    else NativeMethods.SetProcessDPIAware();

    base.OnStartup(e);
    }
  6. Make sure the rest of your code handles DPI properly. Starting with .NET 4.6.2 you can use the OnDpiChanged event and VisualTreeHelper.GetDpi() method.

Enjoy :)

DPIAware and Enable application framework at Application Settings

Enable the Win Version like TnTinMn said:

If you want use the app.config file to declare DPI awareness for .Net
4.7 and above, you also have to modify the app.manifest file (Project Properties->Application->View Window Settings button) and declare the
program to be Win 10 compatible. See: Configuring your Windows Forms app
for high DPI support. If using the app.config method, do not set the
dpi level in the app.manifest file as that will override the
app.config settings.

Before we take a newly generated mainfest file and add the settings we need since years. We start the app as "application framework enable" in the Project Settings. Then we have to move the shared main code to the main form. I think this should do the job. thx at all.

Form messed up when DPI above 96 and specific method is called

Disabling the DPI awareness based on @Jimi suggestion worked for me. I ended up using code from this answer: WPF ClickOnce DPI awareness Per-Monitor v2 from @Marko



Related Topics



Leave a reply



Submit