DpiRescaler.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. using System.Collections.Generic;
  2. using System.Windows.Input;
  3. namespace System.Windows.Forms
  4. {
  5. public static class DpiRescaler
  6. {
  7. public static void Install(Window window, Control control, System.Windows.UIElement wpfControl, EventHandler handler)
  8. {
  9. if (window == null)
  10. return;
  11. window.DpiChanged += (sender, e) =>
  12. {
  13. // this event is fired when current dpi is changed:
  14. // - when you move a window to another monitor with different dpi;
  15. // - when you change dpi while window is open;
  16. // - when you have changed dpi while app is running and you open a new window;
  17. // - if you moved an app to another monitor and open a new window.
  18. // For the two latter cases, window is created using the default dpi (default monitor dpi).
  19. // When a window is about to show, WPF scales its controls and fires DpiChanged event.
  20. // We need some more work to make docking/anchor system behave correctly on rescale.
  21. // we need only event coming from the window
  22. if (e.Source == window)
  23. {
  24. var controls = new List<Control>();
  25. // suspend layout of all form controls before proceed.
  26. EnumControls(control, c =>
  27. {
  28. c.BeforeDpiChange();
  29. // collect processed controls
  30. controls.Add(c);
  31. });
  32. // hide the root form control to avoid weird visual effects (partially rendered controls during dpi change)
  33. wpfControl.Visibility = Visibility.Hidden;
  34. // this action will be performed after the form is rescaled completely. Async is required
  35. window.Dispatcher.InvokeAsync(() =>
  36. {
  37. // original code was:
  38. // float rescale = DeviceDpi / AutoScaleDimensions.Width;
  39. // we need to check this case (bug: FR dialog form designer does not scale properly)
  40. float rescale = control is ContainerControl container ?
  41. container.DeviceDpi / container.AutoScaleDimensions.Width :
  42. (float)(e.NewDpi.PixelsPerInchX / e.OldDpi.PixelsPerInchX);
  43. // process controls in reverse order to match Suspend/Resume calls (important, case: StdReportWizard bad rescale on pmV2)
  44. controls.Reverse();
  45. controls.ForEach(c => c.AfterDpiChange(rescale));
  46. handler(control, e);
  47. // restore the root control visibility, do it cool
  48. wpfControl.Visibility = Visibility.Visible;
  49. wpfControl.AnimateOpacity(0, 1, 100);
  50. // hiding/showing the container causes focus lost. Restore the focus (issue with FR text editor showing on non-primary monitor)
  51. FocusManager.GetFocusedElement(window)?.Focus();
  52. });
  53. void EnumControls(Control control, Action<Control> action)
  54. {
  55. action(control);
  56. foreach (Control c in control.Controls)
  57. EnumControls(c, action);
  58. }
  59. }
  60. };
  61. }
  62. }
  63. }