Skip to content

[API Proposal] Automatic DPI Awareness via ModuleInitializer (Plug & Play) #14622

@diegoxd12wee436

Description

@diegoxd12wee436

Background and motivation

Currently, enabling high DPI awareness (PerMonitorV2) in WinForms applications requires manual setup in Program.cs using Application.SetHighDpiMode. This approach has several limitations:

Developers often forget to set it or set it incorrectly.

It does not handle runtime DPI changes when moving between monitors with different scaling.

Custom controls that use SkiaSharp or direct GDI+ drawing need additional code to rescale properly.

This proposal introduces a zero-configuration, plug-and-play solution that automatically enables PerMonitorV2 DPI awareness as soon as the assembly loads (via ModuleInitializer), and provides a clean pattern for handling runtime DPI changes in custom controls. The solution works across .NET Framework 4.8 to .NET 8+.

Since it worked for me im sending you this (im using a translator sorry if some things sounds weird that would be the translator XD im learning english)anyways hope this helps you :')

API Proposal

#nullable enable
using System.Diagnostics;
using System.Runtime.InteropServices;

// 🔥 FIX DE COMPATIBILIDAD MULTI-TARGET (Resuelve la advertencia CS0436)
// Solo inyectamos este atributo si el framework es más viejo que .NET 5 (ej. .NET 4.8). 
// .NET 8 ya lo trae de fábrica, así que lo ignora mágicamente.
#if !NET5_0_OR_GREATER
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
    internal sealed class ModuleInitializerAttribute : Attribute { }
}
#endif

namespace FluentWinForms.Core
{
    /// <summary>
    /// El motor de arranque fantasma. 
    /// Se auto-ejecuta cuando la DLL se carga en memoria (Plug & Play real).
    /// </summary>
    public static class FluentEngine
    {
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetProcessDpiAwarenessContext(int dpiFlag);

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

        private static bool _isInitialized = false;

        // 🔥 ESTA ETIQUETA HACE QUE SE EJECUTE SOLO AL INSTALAR EL NUGET
        [System.Runtime.CompilerServices.ModuleInitializer]
        public static void Initialize()
        {
            if (_isInitialized) return;

            ForceHighDpiAwareness();

            // Iniciamos el tracker del Tema de Windows automáticamente en segundo plano
           // AppTheme.SyncWithSystemTheme(); // (only for my engine, not needed for DPI)
            

            _isInitialized = true;
            Trace.WriteLine("[FluentWinForms] Motor auto-inicializado en Alto Rendimiento (Plug & Play).");
        }

        private static void ForceHighDpiAwareness()
        {
            try
            {
                // Intentamos activar "PerMonitorV2" (Soporte Retina para Windows 10/11)
                // -4 equivale a DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
                if (!SetProcessDpiAwarenessContext(-4))
                {
                    // Fallback para Windows antiguos (Windows 7 / 8)
                    SetProcessDPIAware();
                }
            }
            catch
            {
                // Silenciamos si hay restricciones extremas en el SO del usuario
            }
        }
    }
}

API Usage

Step 1: Automatic DPI initialization (consumer perspective)

A developer just needs to reference the library. The ModuleInitializer runs automatically when the assembly loads:

// No code needed in Program.cs!
// The library sets PerMonitorV2 DPI awareness automatically.

Step 2: Handling runtime DPI changes in custom controls

public class MyCustomControl : Control
{
    private float _dpiScale = 1.0f;
    private SKBitmap? _bitmap;

    protected float S(float value) => value * _dpiScale;

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        using (Graphics g = CreateGraphics())
            _dpiScale = g.DpiX / 96f;
        RecreateBitmap();
    }

    protected override void OnDpiChangedAfterParent(EventArgs e)
    {
        base.OnDpiChangedAfterParent(e);
        using (Graphics g = CreateGraphics())
            _dpiScale = g.DpiX / 96f;
        _bitmap?.Dispose();
        RecreateBitmap();
        Invalidate();
    }

    private void RecreateBitmap()
    {
        int scaledWidth = (int)S(Width);
        int scaledHeight = (int)S(Height);
        _bitmap = new SKBitmap(scaledWidth, scaledHeight);
    }
}

Step 3: Native WinForms controls work automatically

Standard controls (Button, TextBox, DataGridView, etc.) scale automatically because the process is DPI-aware. No additional code is required.

Alternative Designs

Manual approach (current status quo):
Calling Application.SetHighDpiMode(HighDpiMode.PerMonitorV2) in Program.cs.
Downside: No runtime DPI change handling; easy to forget; requires extra code.

app.config manifest approach:
Declaring DPI awareness in the application manifest.
Downside: Not flexible; doesn't handle runtime changes; requires manifest editing.

Per-control scaling using DpiChanged event:
Subscribing to the DpiChanged event on each control.
Downside: Verbose; easy to miss controls; high maintenance.

The proposed solution combines the best of both worlds: automatic initialization at the process level + a clean, reusable pattern for custom control authors.

Risks

Breaking changes: None. The solution is additive and opt-in via library reference.

Performance: The ModuleInitializer runs once at assembly load (negligible overhead). The OnDpiChangedAfterParent handler runs only when the control's DPI changes (rare event). The S() helper is a simple multiplication.

Compatibility:

Works on Windows 10/11 with PerMonitorV2.

Falls back to SetProcessDPIAware on older Windows (7/8).

Silently fails if the OS doesn't support DPI awareness.

The conditional ModuleInitializerAttribute ensures compilation on .NET Framework 4.8 without errors.

Potential issues:
Some very old applications (Windows 7 without updates) may not support SetProcessDpiAwarenessContext. The try-catch handles this gracefully with a fallback.

Testing: Verified on .NET Framework 4.8, and .NET 8, with both standard WinForms controls and SkiaSharp-based custom rendering.

Will this feature affect UI controls?

Yes, but positively: all controls (both standard and custom) become fully DPI-aware.

VS Designer compatibility: Works without changes. The ModuleInitializer does not run in the designer (only when the assembly is loaded at runtime). The OnHandleCreated and OnDpiChangedAfterParent methods only execute at runtime, not during design-time.

Accessibility impact: No negative impact. High DPI support improves accessibility for users with high-resolution displays.

Localization impact: None. DPI scaling is independent of localization. The solution does not introduce any strings or localizable resources.

VideoCompressorResizeCompressVideo2026_06_08_06_47_07.mp4

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestion(1) Early API idea and discussion, it is NOT ready for implementationarea-HDPI-SAIssues related to high DPI SystemAware modeuntriagedThe team needs to look at this issue in the next triage

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions