@@ -13,8 +13,17 @@ public class NativeMethods
1313 public const int WS_THICKFRAME = 0x00040000 ;
1414 public const int SWP_NOZORDER = 0x0004 ;
1515 public const int SWP_NOACTIVATE = 0x0010 ;
16+ public const int SWP_SHOWWINDOW = 0x0040 ;
1617 public const long WS_POPUP = 0x80000000L ;
1718 public const long WS_CAPTION = 0x00C00000L ;
19+ public const long WS_CHILD = 0x40000000L ;
20+ public const uint MONITOR_DEFAULTTONEAREST = 0x00000002 ;
21+
22+ /// <summary>Places the window at the bottom of the Z order (behind all others).</summary>
23+ public static readonly IntPtr HWND_BOTTOM = new IntPtr ( 1 ) ;
24+
25+ /// <summary>The value returned by CreateFile on failure.</summary>
26+ public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr ( - 1 ) ;
1827
1928 #endregion
2029
@@ -42,6 +51,57 @@ public enum WM : uint
4251 SYSCOMMAND = 0x0112
4352 }
4453
54+ /// <summary>
55+ /// Controls how a thread hosts child windows with different DPI awareness contexts.
56+ /// Set to <see cref="DPI_HOSTING_BEHAVIOR_MIXED"/> before calling SetParent with a
57+ /// cross-process child window to enable DPI notification forwarding.
58+ /// Available on Windows 10 1803 (build 17134) and later.
59+ /// </summary>
60+ public enum DPI_HOSTING_BEHAVIOR
61+ {
62+ DPI_HOSTING_BEHAVIOR_INVALID = - 1 ,
63+ DPI_HOSTING_BEHAVIOR_DEFAULT = 0 ,
64+ DPI_HOSTING_BEHAVIOR_MIXED = 1
65+ }
66+
67+ #endregion
68+
69+ #region Structs
70+
71+ [ StructLayout ( LayoutKind . Sequential ) ]
72+ public struct RECT
73+ {
74+ public int left , top , right , bottom ;
75+ }
76+
77+ [ StructLayout ( LayoutKind . Sequential , CharSet = CharSet . Auto ) ]
78+ public struct MONITORINFO
79+ {
80+ public int cbSize ;
81+ public RECT rcMonitor ;
82+ public RECT rcWork ;
83+ public uint dwFlags ;
84+ }
85+
86+ [ StructLayout ( LayoutKind . Sequential ) ]
87+ public struct COORD
88+ {
89+ public short X ;
90+ public short Y ;
91+ }
92+
93+ [ StructLayout ( LayoutKind . Sequential , CharSet = CharSet . Unicode ) ]
94+ public struct CONSOLE_FONT_INFOEX
95+ {
96+ public uint cbSize ;
97+ public uint nFont ;
98+ public COORD dwFontSize ;
99+ public uint FontFamily ;
100+ public uint FontWeight ;
101+ [ MarshalAs ( UnmanagedType . ByValTStr , SizeConst = 32 ) ]
102+ public string FaceName ;
103+ }
104+
45105 #endregion
46106
47107 #region Pinvoke/Win32 Methods
@@ -88,5 +148,105 @@ public static extern IntPtr SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, in
88148 [ DllImport ( "user32.dll" , CharSet = CharSet . Auto , SetLastError = true ) ]
89149 public static extern bool SetForegroundWindow ( IntPtr hWnd ) ;
90150
151+ /// <summary>
152+ /// Sets the DPI hosting behavior for windows created or reparented on the calling thread.
153+ /// Call with <see cref="DPI_HOSTING_BEHAVIOR.DPI_HOSTING_BEHAVIOR_MIXED"/> before
154+ /// SetParent to opt into mixed-DPI hosting and enable DPI notification routing for
155+ /// child windows. Returns the previous behavior so it can be restored.
156+ /// Windows 10 1803+ only.
157+ /// </summary>
158+ [ DllImport ( "user32.dll" ) ]
159+ public static extern DPI_HOSTING_BEHAVIOR SetThreadDpiHostingBehavior ( DPI_HOSTING_BEHAVIOR value ) ;
160+
161+ [ DllImport ( "user32.dll" ) ]
162+ public static extern IntPtr MonitorFromWindow ( IntPtr hWnd , uint dwFlags ) ;
163+
164+ [ DllImport ( "user32.dll" , CharSet = CharSet . Auto ) ]
165+ public static extern bool GetMonitorInfo ( IntPtr hMonitor , ref MONITORINFO lpmi ) ;
166+
167+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
168+ public static extern bool AttachConsole ( uint dwProcessId ) ;
169+
170+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
171+ public static extern bool FreeConsole ( ) ;
172+
173+ [ DllImport ( "kernel32.dll" , SetLastError = true , CharSet = CharSet . Unicode ) ]
174+ private static extern IntPtr CreateFile ( string lpFileName , uint dwDesiredAccess , uint dwShareMode ,
175+ IntPtr lpSecurityAttributes , uint dwCreationDisposition , uint dwFlagsAndAttributes , IntPtr hTemplateFile ) ;
176+
177+ [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
178+ private static extern bool CloseHandle ( IntPtr hObject ) ;
179+
180+ [ DllImport ( "kernel32.dll" , CharSet = CharSet . Unicode , SetLastError = true ) ]
181+ private static extern bool GetCurrentConsoleFontEx ( IntPtr hConsoleOutput , bool bMaximumWindow ,
182+ ref CONSOLE_FONT_INFOEX lpConsoleCurrentFontEx ) ;
183+
184+ [ DllImport ( "kernel32.dll" , CharSet = CharSet . Unicode , SetLastError = true ) ]
185+ private static extern bool SetCurrentConsoleFontEx ( IntPtr hConsoleOutput , bool bMaximumWindow ,
186+ ref CONSOLE_FONT_INFOEX lpConsoleCurrentFontEx ) ;
187+
188+ #endregion
189+
190+ #region Helpers
191+
192+ /// <summary>
193+ /// Returns the physical-pixel bounding rectangle of the monitor that contains
194+ /// the specified window (nearest monitor if the window is off-screen).
195+ /// </summary>
196+ public static RECT GetMonitorBoundsForWindow ( IntPtr hWnd )
197+ {
198+ var hMonitor = MonitorFromWindow ( hWnd , MONITOR_DEFAULTTONEAREST ) ;
199+ var mi = new MONITORINFO { cbSize = Marshal . SizeOf < MONITORINFO > ( ) } ;
200+ GetMonitorInfo ( hMonitor , ref mi ) ;
201+ return mi . rcMonitor ;
202+ }
203+
204+ /// <summary>
205+ /// Attaches to <paramref name="processId"/>'s console and rescales its current font
206+ /// by <paramref name="scaleFactor"/> using <c>SetCurrentConsoleFontEx</c>.
207+ /// This is a cross-process-safe approach that bypasses WM_DPICHANGED message passing
208+ /// entirely. Works for any conhost-based console (PowerShell, cmd, etc.).
209+ /// </summary>
210+ public static void TryRescaleConsoleFont ( uint processId , double scaleFactor )
211+ {
212+ if ( Math . Abs ( scaleFactor - 1.0 ) < 0.01 )
213+ return ;
214+
215+ if ( ! AttachConsole ( processId ) )
216+ return ;
217+
218+ const uint GENERIC_READ_WRITE = 0xC0000000u ;
219+ const uint FILE_SHARE_READ_WRITE = 3u ;
220+ const uint OPEN_EXISTING = 3u ;
221+
222+ var hOut = CreateFile ( "CONOUT$" , GENERIC_READ_WRITE , FILE_SHARE_READ_WRITE ,
223+ IntPtr . Zero , OPEN_EXISTING , 0u , IntPtr . Zero ) ;
224+
225+ try
226+ {
227+ if ( hOut == INVALID_HANDLE_VALUE )
228+ return ;
229+
230+ try
231+ {
232+ var fi = new CONSOLE_FONT_INFOEX { cbSize = ( uint ) Marshal . SizeOf < CONSOLE_FONT_INFOEX > ( ) } ;
233+ if ( GetCurrentConsoleFontEx ( hOut , false , ref fi ) )
234+ {
235+ fi . dwFontSize . Y = ( short ) Math . Max ( 1 , ( int ) Math . Round ( fi . dwFontSize . Y * scaleFactor ) ) ;
236+ fi . cbSize = ( uint ) Marshal . SizeOf < CONSOLE_FONT_INFOEX > ( ) ;
237+ SetCurrentConsoleFontEx ( hOut , false , ref fi ) ;
238+ }
239+ }
240+ finally
241+ {
242+ CloseHandle ( hOut ) ;
243+ }
244+ }
245+ finally
246+ {
247+ FreeConsole ( ) ;
248+ }
249+ }
250+
91251 #endregion
92252}
0 commit comments