UiScaler.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows;
  4. using System.Windows.Media;
  5. using Microsoft.Win32;
  6. namespace TeamAAS
  7. {
  8. /// <summary>
  9. /// 窗口分辨率/DPI 自适应助手。
  10. /// </summary>
  11. public static class UiScaler
  12. {
  13. private static double _globalScale = 1.0;
  14. /// <summary>当前全局缩放因子(弹窗读取用)</summary>
  15. public static double GlobalScale => _globalScale;
  16. /// <summary>全局因子变化通知(弹窗订阅后自动跟随)</summary>
  17. public static event Action ScaleChanged;
  18. private static readonly Dictionary<Window, Size> _designSizes = new Dictionary<Window, Size>();
  19. /// <summary>
  20. /// 主窗口:计算全局缩放因子并应用(设计基准默认 1600x900,钳制 minScale~maxScale)。
  21. /// </summary>
  22. public static void AttachMain(Window window, double refWidth = 1600, double refHeight = 900,
  23. double minScale = 0.6, double maxScale = 2.0)
  24. {
  25. if (window == null) return;
  26. var root = window.Content as FrameworkElement;
  27. if (root == null) return;
  28. if (root.Tag is UiScalerToken) return;
  29. root.Tag = new UiScalerToken();
  30. void Update()
  31. {
  32. double w = window.ActualWidth > 0 ? window.ActualWidth : refWidth;
  33. double h = window.ActualHeight > 0 ? window.ActualHeight : refHeight;
  34. double f = Math.Min(w / refWidth, h / refHeight);
  35. _globalScale = Math.Max(minScale, Math.Min(maxScale, f));
  36. ApplyTransform(window, _globalScale, resizeWindow: false);
  37. ScaleChanged?.Invoke();
  38. }
  39. window.SizeChanged += (s, e) => Update();
  40. window.DpiChanged += (s, e) => Update();
  41. window.Loaded += (s, e) => Update();
  42. SystemEvents.DisplaySettingsChanged += (s, e) => window.Dispatcher.Invoke(Update);
  43. }
  44. /// <summary>
  45. /// 弹窗/子窗体:跟随全局缩放因子(只放大不缩小,窗口尺寸与内容同步缩放)。
  46. /// 幂等:重复调用/全局钩子叠加均安全。
  47. /// </summary>
  48. public static void Attach(Window window)
  49. {
  50. if (window == null) return;
  51. var root = window.Content as FrameworkElement;
  52. if (root == null) return;
  53. if (root.Tag is UiScalerToken) return;
  54. root.Tag = new UiScalerToken();
  55. Action apply = () => ApplyTransform(window, _globalScale, resizeWindow: true);
  56. apply();
  57. // 全局因子变化时跟随;关闭时退订,避免事件泄漏
  58. ScaleChanged += apply;
  59. window.Closed += (s, e) => ScaleChanged -= apply;
  60. }
  61. private static void ApplyTransform(Window w, double f, bool resizeWindow)
  62. {
  63. var root = w.Content as FrameworkElement;
  64. if (root == null) return;
  65. // 弹窗只放大不缩小(f<1 时保持 1.0,避免小窗被缩到看不清)
  66. double effective = resizeWindow ? Math.Max(1.0, f) : f;
  67. if (Math.Abs(effective - 1.0) < 0.001)
  68. {
  69. root.LayoutTransform = null;
  70. return;
  71. }
  72. if (resizeWindow)
  73. {
  74. // 首次记录设计尺寸(XAML 声明值;NaN=自动/SizeToContent 交由内容自适应)
  75. if (!_designSizes.TryGetValue(w, out var design))
  76. {
  77. design = new Size(w.Width, w.Height);
  78. _designSizes[w] = design;
  79. }
  80. if (!double.IsNaN(design.Width) && design.Width > 0) w.Width = design.Width * effective;
  81. if (!double.IsNaN(design.Height) && design.Height > 0) w.Height = design.Height * effective;
  82. }
  83. root.LayoutTransform = new ScaleTransform(effective, effective);
  84. }
  85. private sealed class UiScalerToken { }
  86. }
  87. }