WindowHelper.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. using System;
  2. using System.Collections;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using System.Security;
  7. using System.Windows;
  8. using System.Windows.Interop;
  9. using HandyControl.Controls;
  10. using HandyControl.Tools.Extension;
  11. using HandyControl.Tools.Helper;
  12. using HandyControl.Tools.Interop;
  13. using Window = System.Windows.Window;
  14. namespace HandyControl.Tools;
  15. public static class WindowHelper
  16. {
  17. /// <summary>
  18. /// 获取当前应用中处于激活的一个窗口
  19. /// </summary>
  20. /// <returns></returns>
  21. public static Window GetActiveWindow()
  22. {
  23. var activeWindow = InteropMethods.GetActiveWindow();
  24. return Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetHandle() == activeWindow);
  25. }
  26. private static readonly BitArray _cacheValid = new((int) InteropValues.CacheSlot.NumSlots);
  27. private static bool _setDpiX = true;
  28. private static bool _dpiInitialized;
  29. private static readonly object _dpiLock = new();
  30. private static int _dpi;
  31. internal static int Dpi
  32. {
  33. [SecurityCritical, SecuritySafeCritical]
  34. get
  35. {
  36. if (!_dpiInitialized)
  37. {
  38. lock (_dpiLock)
  39. {
  40. if (!_dpiInitialized)
  41. {
  42. var desktopWnd = new HandleRef(null, IntPtr.Zero);
  43. // Win32Exception will get the Win32 error code so we don't have to
  44. var dc = InteropMethods.GetDC(desktopWnd);
  45. // Detecting error case from unmanaged call, required by PREsharp to throw a Win32Exception
  46. if (dc == IntPtr.Zero)
  47. {
  48. throw new Win32Exception();
  49. }
  50. try
  51. {
  52. _dpi = InteropMethods.GetDeviceCaps(new HandleRef(null, dc), InteropValues.LOGPIXELSY);
  53. _dpiInitialized = true;
  54. }
  55. finally
  56. {
  57. InteropMethods.ReleaseDC(desktopWnd, new HandleRef(null, dc));
  58. }
  59. }
  60. }
  61. }
  62. return _dpi;
  63. }
  64. }
  65. private static int _dpiX;
  66. internal static int DpiX
  67. {
  68. [SecurityCritical, SecuritySafeCritical]
  69. get
  70. {
  71. if (_setDpiX)
  72. {
  73. lock (_cacheValid)
  74. {
  75. if (_setDpiX)
  76. {
  77. _setDpiX = false;
  78. var desktopWnd = new HandleRef(null, IntPtr.Zero);
  79. var dc = InteropMethods.GetDC(desktopWnd);
  80. if (dc == IntPtr.Zero)
  81. {
  82. throw new Win32Exception();
  83. }
  84. try
  85. {
  86. _dpiX = InteropMethods.GetDeviceCaps(new HandleRef(null, dc), InteropValues.LOGPIXELSX);
  87. _cacheValid[(int) InteropValues.CacheSlot.DpiX] = true;
  88. }
  89. finally
  90. {
  91. InteropMethods.ReleaseDC(desktopWnd, new HandleRef(null, dc));
  92. }
  93. }
  94. }
  95. }
  96. return _dpiX;
  97. }
  98. }
  99. private static Thickness _windowResizeBorderThickness;
  100. internal static Thickness WindowResizeBorderThickness
  101. {
  102. [SecurityCritical]
  103. get
  104. {
  105. lock (_cacheValid)
  106. {
  107. while (!_cacheValid[(int) InteropValues.CacheSlot.WindowResizeBorderThickness])
  108. {
  109. _cacheValid[(int) InteropValues.CacheSlot.WindowResizeBorderThickness] = true;
  110. var frameSize = new Size(InteropMethods.GetSystemMetrics(InteropValues.SM.CXSIZEFRAME), InteropMethods.GetSystemMetrics(InteropValues.SM.CYSIZEFRAME));
  111. var frameSizeInDips = DpiHelper.DeviceSizeToLogical(frameSize, DpiX / 96.0, Dpi / 96.0);
  112. _windowResizeBorderThickness = new Thickness(frameSizeInDips.Width, frameSizeInDips.Height, frameSizeInDips.Width, frameSizeInDips.Height);
  113. }
  114. }
  115. return _windowResizeBorderThickness;
  116. }
  117. }
  118. public static Thickness WindowMaximizedPadding
  119. {
  120. get
  121. {
  122. InteropValues.APPBARDATA appBarData = default;
  123. var autoHide = InteropMethods.SHAppBarMessage(4, ref appBarData) != 0;
  124. #if NET40
  125. return WindowResizeBorderThickness.Add(new Thickness(autoHide ? -8 : 0));
  126. #elif NETCOREAPP
  127. var hdc = InteropMethods.GetDC(IntPtr.Zero);
  128. var scale = InteropMethods.GetDeviceCaps(hdc, InteropValues.DESKTOPVERTRES) / (float) InteropMethods.GetDeviceCaps(hdc, InteropValues.VERTRES);
  129. InteropMethods.ReleaseDC(IntPtr.Zero, hdc);
  130. return WindowResizeBorderThickness.Add(new Thickness((autoHide ? -4 : 4) * scale));
  131. #else
  132. return WindowResizeBorderThickness.Add(new Thickness(autoHide ? -4 : 4));
  133. #endif
  134. }
  135. }
  136. public static IntPtr CreateHandle() => new WindowInteropHelper(new Window()).EnsureHandle();
  137. public static IntPtr GetHandle(this Window window) => new WindowInteropHelper(window).EnsureHandle();
  138. public static HwndSource GetHwndSource(this Window window) => HwndSource.FromHwnd(window.GetHandle());
  139. /// <summary>
  140. /// 让窗口激活作为前台最上层窗口
  141. /// </summary>
  142. /// <param name="window"></param>
  143. public static void SetWindowToForeground(Window window)
  144. {
  145. // [WPF 让窗口激活作为前台最上层窗口的方法 - lindexi - 博客园](https://www.cnblogs.com/lindexi/p/12749671.html)
  146. var interopHelper = new WindowInteropHelper(window);
  147. var thisWindowThreadId = InteropMethods.GetWindowThreadProcessId(interopHelper.Handle, out _);
  148. var currentForegroundWindow = InteropMethods.GetForegroundWindow();
  149. var currentForegroundWindowThreadId = InteropMethods.GetWindowThreadProcessId(currentForegroundWindow, out _);
  150. // [c# - Bring a window to the front in WPF - Stack Overflow](https://stackoverflow.com/questions/257587/bring-a-window-to-the-front-in-wpf )
  151. // [SetForegroundWindow的正确用法 - 子坞 - 博客园](https://www.cnblogs.com/ziwuge/archive/2012/01/06/2315342.html )
  152. /*
  153. 1.得到窗口句柄FindWindow
  154. 2.切换键盘输入焦点AttachThreadInput
  155. 3.显示窗口ShowWindow(有些窗口被最小化/隐藏了)
  156. 4.更改窗口的Z Order,SetWindowPos使之最上,为了不影响后续窗口的Z Order,改完之后,再还原
  157. 5.最后SetForegroundWindow
  158. */
  159. InteropMethods.AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true);
  160. window.Show();
  161. window.Activate();
  162. // 去掉和其他线程的输入链接
  163. InteropMethods.AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false);
  164. // 用于踢掉其他的在上层的窗口
  165. if (window.Topmost != true)
  166. {
  167. window.Topmost = true;
  168. window.Topmost = false;
  169. }
  170. }
  171. /// <summary>
  172. /// 开始使用触摸拖动窗口,在触摸抬起后自动结束
  173. /// </summary>
  174. public static void TouchDragMove(this Window window) => new TouchDragMoveWindowHelper(window).Start();
  175. public static void StartFullScreen(this Window window) => FullScreenHelper.StartFullScreen(window);
  176. public static void EndFullScreen(this Window window) => FullScreenHelper.EndFullScreen(window);
  177. }