Winforms: Setting/Getting HighDpiMode in WinForms Core Apps

Created on 4 Dec 2018  Â·  14Comments  Â·  Source: dotnet/winforms

Rationale

Developers in WinForms Core currently have no way to set the HighDPI mode in their WinForms App. In a future version of .NET Core 3.0, this will again be possible via App.Manifest; however, a change in the settings requires then their app to be rebuilt. The previous option in the classic framework to control this setting via App.Config has been completely dropped in .NET Core. Currently, the only way to set the HighDPIMode is via P/Invoke. The Application.SetHighDpiMode method and Application.HighDpiMode property are an easy to apply and to discover alternative.

Proposed API

```c#
namespace System.Windows.Forms
{
public sealed class Application
{
// Existing methods/properties (excerpt)
public static void EnableVisualStyles();
public static bool RenderWithVisualStyles { get; }

    // Proposed method/property
    public static bool SetHighDpiMode(HighDpiMode highDpiMode);
    public static HighDpiMode HighDpiMode { get; }
}

public enum HighDpiMode
{
    DpiUnaware,
    SystemAware,
    PerMonitor,
    PerMonitorV2,
    DpiUnawareGdiScaled
}

}
```

Description

Following EnableVisualStyles()/RenderWithVisualStyles of the Application class, we propose the method SetHighDpiMode which switches to the corresponding HighDPI mode, if that HighDPIMode setting a) has not been set before by other means (App.Manifest, P/Invoke before Application.Run – in this case the value to be set is ignored), and b) that HighDPIMode setting was set via SetHighDpiMode before calling Application.Run (or the first UI Element has been rendered).

Possible settings values are provided by the HighDpiMode Enum:

  • DpiUnaware
  • SystemAware
  • PerMonitor
  • PerMonitorV2
  • DpiUnawareGdiScaled

If a setting is attempted to be set which is not supported by the underlying OS, SetHighDpiMode automatically assumes the next possible setting on that OS; SetHighDpiMode never directly triggers an exception, but gives feedback as to whether the setting could be successfully set (true) or not (false).

In addition developers can query the actual setting of the current HighDpiMode at any time using the Application.HighDpiMode property, which returns one of the available HighDpiMode Enum values.

In the current implementation, we should limit the method/property to the process DpiAwareness rather than a Window DpiAwareness, and should probably think about extending this to a static method/property of the Control class to the windows DpiAwareness (@Tanya-Solyanik, thoughts?).

api-approved

Most helpful comment

FYI, steps to set DPI Awareness using the manifest file:

  • Add application manifest to your .NET Core project.

  • Uncomment the DPI-Awareness element, dpiAwareness element is supported as well., as manifest resource is processed by the windows code same as it is processed for a native app.

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

  • Uncomment Windows10 as a supported OS
    <!-- Windows 10 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  • Add this manifest to your project file
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
  <PropertyGroup>
   <Win32Manifest>app1.manifest</Win32Manifest>
   </PropertyGroup>
</Project>
  • dotnet run

All 14 comments

FYI, steps to set DPI Awareness using the manifest file:

  • Add application manifest to your .NET Core project.

  • Uncomment the DPI-Awareness element, dpiAwareness element is supported as well., as manifest resource is processed by the windows code same as it is processed for a native app.

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

  • Uncomment Windows10 as a supported OS
    <!-- Windows 10 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  • Add this manifest to your project file
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
  <PropertyGroup>
   <Win32Manifest>app1.manifest</Win32Manifest>
   </PropertyGroup>
</Project>
  • dotnet run

This eliminates lot of trouble.

We'd still need to recompile apps for changes to be picked up, though.
(Or do we?)

Why is this API (or the manifest) required at all? The winforms code can just call the corresponding windows api to make the application per monitor dpi aware at launch. What would be the reason to support non dpi aware apps in 2018?

Because we have several HighDPI modes, which we unfortunately don't support all completely yet.

We tend to go for SystenAware by default - currently it is DpiUnaware. Ultimately, we want PerMonitorV2, but there is still some work to be done before we could default to that.

On top: folks need to be prepared in their apps for backwards compatibility. If you have a lot of owner drawing going on, SystemAware is likely to break you, if you only took 96dpi into account to begin with. So, having control over the HighDPI mode (even at runtime) is often essential.

      We'd still need to recompile apps for changes to be picked up, though.

(Or do we?)

Right. as this change in csproj. Folks migrating to core recompiles anyway.

      Why is this API (or the manifest) required at all? The winforms code can just call the corresponding windows api to make the application per monitor dpi aware at launch. What would be the reason to support non dpi aware apps in 2018?

This approach is cleaner. Dpi handling in winforms happen in the very begining. We may be able to set it programatically in the application but if users want to change it, our dpi related values are cached and may not respond as expected. ALso, when winforms application launched by a host process ( for ex: Debugging from VS), All dpi related values are cached much before we get to the starting point of winforms application.

I agree that supporting high dpi is currently very challenging (to put it nicely) in winforms. I did a quick search in the winforms codebase and there is a lot of code in the individual controls to deal with this issue. I think the best (maybe only?) way to actually fix it completely would be to use effective pixels like on UWP/WPF. This way, most applications will not have to deal with handling different scaling factor at all (maybe only provide bitmap images in multiple sizes). The transformation between effective and physical pixels can then be handled by the low level rendering code.

@Tanya-Solyanik: Until now, our HighDpiMode enum looks like this.

c# public enum HighDpiMode { Uninitialized, DpiUnaware, SystemAware, PerMonitor, PerMonitorV2 }

I could imagine for addressing HighDPI issue in scenarios like the Document class in Windows 10 1809, we would need to take DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED also into account. So, do you think we should already add an additional HighDpiMode enum value UnawareGdiScaled?

(See: https://docs.microsoft.com/en-us/windows/desktop/hidpi/dpi-awareness-context)

adding an enum value is easy. the difficult part will be to implement support for these values. It does not matter if the value is added now or later. A more interesting question is - how well does _GDISCALED mode work with winforms. I had not tested it. We are doing double-buffering, so I'm afraid that GDI can't access the DC we are rendering into...

Another point about the enum worth noting here is that if you send Uninitalized to windows, for example, to set the thread context, windows will refuse to do this; so we will have to make sure we're checking for this kind of thing. I would prefer to live in a world where we track such things with a boolean of some kind instead of part of the enum, but this is not the end of the world.

Video

  • It's a bit odd to use a method; one would expect a regular property setter.
  • However, after reading the text it seems that's because the method might ignore the value. Presumably the method returns false in this case, as well as in your case (a)?
  • Thus, the shape looks good as proposed.

Are we removing "Uninitialized" value from the enum and adding DpiUnawareGdiScaled? If yes, @KlausLoeffelmann could you update the initial description in the very top?

Sure.

Was this page helpful?
0 / 5 - 0 ratings