using System.Collections.Generic; using System.Windows.Input; namespace System.Windows.Forms { public static class DpiRescaler { public static void Install(Window window, Control control, System.Windows.UIElement wpfControl, EventHandler handler) { if (window == null) return; window.DpiChanged += (sender, e) => { // this event is fired when current dpi is changed: // - when you move a window to another monitor with different dpi; // - when you change dpi while window is open; // - when you have changed dpi while app is running and you open a new window; // - if you moved an app to another monitor and open a new window. // For the two latter cases, window is created using the default dpi (default monitor dpi). // When a window is about to show, WPF scales its controls and fires DpiChanged event. // We need some more work to make docking/anchor system behave correctly on rescale. // we need only event coming from the window if (e.Source == window) { var controls = new List(); // suspend layout of all form controls before proceed. EnumControls(control, c => { c.BeforeDpiChange(); // collect processed controls controls.Add(c); }); // hide the root form control to avoid weird visual effects (partially rendered controls during dpi change) wpfControl.Visibility = Visibility.Hidden; // this action will be performed after the form is rescaled completely. Async is required window.Dispatcher.InvokeAsync(() => { // original code was: // float rescale = DeviceDpi / AutoScaleDimensions.Width; // we need to check this case (bug: FR dialog form designer does not scale properly) float rescale = control is ContainerControl container ? container.DeviceDpi / container.AutoScaleDimensions.Width : (float)(e.NewDpi.PixelsPerInchX / e.OldDpi.PixelsPerInchX); // process controls in reverse order to match Suspend/Resume calls (important, case: StdReportWizard bad rescale on pmV2) controls.Reverse(); controls.ForEach(c => c.AfterDpiChange(rescale)); handler(control, e); // restore the root control visibility, do it cool wpfControl.Visibility = Visibility.Visible; wpfControl.AnimateOpacity(0, 1, 100); // hiding/showing the container causes focus lost. Restore the focus (issue with FR text editor showing on non-primary monitor) FocusManager.GetFocusedElement(window)?.Focus(); }); void EnumControls(Control control, Action action) { action(control); foreach (Control c in control.Controls) EnumControls(c, action); } } }; } } }