我的WPF应用程序在我的两台显示器笔记本电脑开发系统上显示出奇怪的行为 第二台显示器的分辨率为1920 x 1080; 笔记本电脑的分辨率是1366 x 768.笔记本电脑运行的是Windows 8.1,两台显示器的DPI设置都设置为100%。 插入时,第二台显示器是主显示器。 很明显,当第二台显示器没有插上时,笔记本电脑的显示器就是主显示器。
应用程序窗口始终最大化,但可以最小化。 它不能被拖动当你插入第二个显示器或拔下插头时,问题与显示器从一个显示器移动到另一个显示器时的显示方式有关。
当插入第二台显示器启动程序时,拔下笔记本电脑后,它将移动到笔记本电脑的显示器上。 WPF代码也正确处理了这种改变。 也就是说,它检测到原来的大小不适合新的显示器,所以它重新绘制它适合。 当第二台监视器重新插入时,它会移回第二台监视器,并以适当的尺寸重新绘制该监视器。 这正是我在这种情况下想要的。 问题是当程序在其他configuration中启动时。
当程序启动时没有插入第二个显示器,它将以笔记本电脑显示器的适当尺寸绘制。 当程序正在运行时插入第二台显示器,窗口移动到第二台显示器,但绘制错误。 由于该程序是最大化的,它有一个巨大的黑色边框在三面围绕它的内容显示在笔记本电脑的显示器相同的大小区域。
编辑:我刚刚完成了一些testing,并且WPF似乎没有正确处理从较小分辨率到较高分辨率的分辨率更改。 该窗口的行为是相同的,当我在笔记本电脑的显示器上启动程序,然后插入第二个显示器。 至less它是一致的。
我发现通过处理SystemEvents.DisplaySettingsChanged
事件,我可以SystemEvents.DisplaySettingsChanged
第二个监视器插入的时间或屏幕分辨率更改的SystemEvents.DisplaySettingsChanged
。 在我的testing中,我发现当窗口从较小的显示器移动到较大的窗口时,当窗口移动到较大的窗口时, Width
, Height
, ActualWidth
和ActualHeight
不变。 我已经能够做到的最好的方法是将Height
和Width
属性设置为与监视器的工作区域相匹配的值,但是ActualWidth
和ActualHeight
属性不会更改。
我如何强迫这个窗口把我的问题看作是一个解决scheme的改变? 或者,我如何强制窗口将其ActualWidth
和ActualHeight
属性ActualHeight
为正确的值?
窗口从我写的名为DpiAwareWindow的类中下来:
public class DpiAwareWindow : Window { private const int LOGPIXELSX = 88; private const int LOGPIXELSY = 90; private const int MONITOR_DEFAULTTONEAREST = 0x00000002; protected enum MonitorDpiType { MDT_Effective_DPI = 0, MDT_Angular_DPI = 1, MDT_Raw_DPI = 2, MDT_Default = MDT_Effective_DPI } public Point CurrentDpi { get; private set; } public bool IsPerMonitorEnabled; public Point ScaleFactor { get; private set; } protected HwndSource source; protected Point systemDpi; protected Point WpfDpi { get; set; } public DpiAwareWindow() : base() { // Watch for SystemEvent notifications SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; // Set up the SourceInitialized event handler SourceInitialized += DpiAwareWindow_SourceInitialized; } ~DpiAwareWindow() { // Deregister our SystemEvents handler SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged; } private void DpiAwareWindow_SourceInitialized( object sender, EventArgs e ) { source = (HwndSource) HwndSource.FromVisual( this ); source.AddHook( WindowProcedureHook ); // Determine if this application is Per Monitor DPI Aware. IsPerMonitorEnabled = GetPerMonitorDPIAware() == ProcessDpiAwareness.Process_Per_Monitor_DPI_Aware; // Is the window in per-monitor DPI mode? if ( IsPerMonitorEnabled ) { // It is. Calculate the DPI used by the System. systemDpi = GetSystemDPI(); // Calculate the DPI used by WPF. WpfDpi = new Point { X = 96.0 * source.CompositionTarget.TransformToDevice.M11, Y = 96.0 * source.CompositionTarget.TransformToDevice.M22 }; // Get the Current DPI of the monitor of the window. CurrentDpi = GetDpiForHwnd( source.Handle ); // Calculate the scale factor used to modify window size, graphics and text. ScaleFactor = new Point { X = CurrentDpi.X / WpfDpi.X, Y = CurrentDpi.Y / WpfDpi.Y }; // Update Width and Height based on the on the current DPI of the monitor Width = Width * ScaleFactor.X; Height = Height * ScaleFactor.Y; // Update graphics and text based on the current DPI of the monitor. UpdateLayoutTransform( ScaleFactor ); } } protected Point GetDpiForHwnd( IntPtr hwnd ) { IntPtr monitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST ); uint newDpiX = 96; uint newDpiY = 96; if ( GetDpiForMonitor( monitor, (int) MonitorDpiType.MDT_Effective_DPI, ref newDpiX, ref newDpiY ) != 0 ) { return new Point { X = 96.0, Y = 96.0 }; } return new Point { X = (double) newDpiX, Y = (double) newDpiY }; } public static ProcessDpiAwareness GetPerMonitorDPIAware() { ProcessDpiAwareness awareness = ProcessDpiAwareness.Process_DPI_Unaware; try { Process curProcess = Process.GetCurrentProcess(); int result = GetProcessDpiAwareness( curProcess.Handle, ref awareness ); if ( result != 0 ) { throw new Exception( "Unable to read process DPI level" ); } } catch ( DllNotFoundException ) { try { // We're running on either Vista, Windows 7 or Windows 8. Return the correct ProcessDpiAwareness value. awareness = IsProcessDpiAware() ? ProcessDpiAwareness.Process_System_DPI_Aware : ProcessDpiAwareness.Process_DPI_Unaware; } catch ( EntryPointNotFoundException ) { } } catch ( EntryPointNotFoundException ) { try { // We're running on either Vista, Windows 7 or Windows 8. Return the correct ProcessDpiAwareness value. awareness = IsProcessDpiAware() ? ProcessDpiAwareness.Process_System_DPI_Aware : ProcessDpiAwareness.Process_DPI_Unaware; } catch ( EntryPointNotFoundException ) { } } // Return the value in awareness. return awareness; } public static Point GetSystemDPI() { IntPtr hDC = GetDC( IntPtr.Zero ); int newDpiX = GetDeviceCaps( hDC, LOGPIXELSX ); int newDpiY = GetDeviceCaps( hDC, LOGPIXELSY ); ReleaseDC( IntPtr.Zero, hDC ); return new Point { X = (double) newDpiX, Y = (double) newDpiY }; } public void OnDPIChanged() { ScaleFactor = new Point { X = CurrentDpi.X / WpfDpi.X, Y = CurrentDpi.Y / WpfDpi.Y }; UpdateLayoutTransform( ScaleFactor ); } public virtual void SystemEvents_DisplaySettingsChanged( object sender, EventArgs e ) { // Get the handle for this window. Need to worry about a window that has been created by not yet displayed. IntPtr handle = source == null ? new HwndSource( new HwndSourceParameters() ).Handle : source.Handle; // Get the current DPI for the window we're on. CurrentDpi = GetDpiForHwnd( handle ); // Adjust the scale factor. ScaleFactor = new Point { X = CurrentDpi.X / WpfDpi.X, Y = CurrentDpi.Y / WpfDpi.Y }; // Update the layout transform UpdateLayoutTransform( ScaleFactor ); } private void UpdateLayoutTransform( Point scaleFactor ) { if ( IsPerMonitorEnabled ) { if ( ScaleFactor.X != 1.0 || ScaleFactor.Y != 1.0 ) { LayoutTransform = new ScaleTransform( scaleFactor.X, scaleFactor.Y ); } else { LayoutTransform = null; } } } public virtual IntPtr WindowProcedureHook( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled ) { // Determine which Monitor is displaying the Window IntPtr monitor = MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST ); // Switch on the message. switch ( (WinMessages) msg ) { case WinMessages.WM_DPICHANGED: // Marshal the value in the lParam into a Rect. RECT newDisplayRect = (RECT) Marshal.PtrToStructure( lParam, typeof( RECT ) ); // Set the Window's position & size. Vector ul = source.CompositionTarget.TransformFromDevice.Transform( new Vector( newDisplayRect.left, newDisplayRect.top ) ); Vector hw = source.CompositionTarget.TransformFromDevice.Transform( new Vector( newDisplayRect.right = newDisplayRect.left, newDisplayRect.bottom - newDisplayRect.top ) ); Left = ul.X; Top = ul.Y; Width = hw.X; Height = hw.Y; // Remember the current DPI settings. Point oldDpi = CurrentDpi; // Get the new DPI settings from wParam CurrentDpi = new Point { X = (double) ( wParam.ToInt32() >> 16 ), Y = (double) ( wParam.ToInt32() & 0x0000FFFF ) }; if ( oldDpi.X != CurrentDpi.X || oldDpi.Y != CurrentDpi.Y ) { OnDPIChanged(); } handled = true; return IntPtr.Zero; case WinMessages.WM_GETMINMAXINFO: // lParam has a pointer to the MINMAXINFO structure. Marshal it into managed memory. MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) ); if ( monitor != IntPtr.Zero ) { MONITORINFO monitorInfo = new MONITORINFO(); GetMonitorInfo( monitor, monitorInfo ); // Get the Monitor's working area RECT rcWorkArea = monitorInfo.rcWork; RECT rcMonitorArea = monitorInfo.rcMonitor; // Adjust the maximized size and position to fit the work area of the current monitor mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left - rcMonitorArea.left ); mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top - rcMonitorArea.top ); mmi.ptMaxSize .x = Math.Abs( rcWorkArea.right - rcWorkArea.left ); mmi.ptMaxSize .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top ); } // Copy our changes to the mmi object back to the original Marshal.StructureToPtr( mmi, lParam, true ); handled = true; return IntPtr.Zero; default: // Let the WPF code handle all other messages. Return 0. return IntPtr.Zero; } } [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )] protected static extern IntPtr GetDC( IntPtr hWnd ); [DllImport( "gdi32.dll", CallingConvention = CallingConvention.StdCall )] protected static extern int GetDeviceCaps( IntPtr hDC, int nIndex ); [DllImport( "shcore.dll", CallingConvention = CallingConvention.StdCall )] protected static extern int GetDpiForMonitor( IntPtr hMonitor, int dpiType, ref uint xDpi, ref uint yDpi ); [DllImport( "user32" )] protected static extern bool GetMonitorInfo( IntPtr hMonitor, MONITORINFO lpmi ); [DllImport( "shcore.dll", CallingConvention = CallingConvention.StdCall )] protected static extern int GetProcessDpiAwareness( IntPtr handle, ref ProcessDpiAwareness awareness ); [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )] protected static extern bool IsProcessDpiAware(); [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )] protected static extern IntPtr MonitorFromWindow( IntPtr hwnd, int flag ); [DllImport( "user32.dll", CallingConvention = CallingConvention.StdCall )] protected static extern void ReleaseDC( IntPtr hWnd, IntPtr hDC ); } public enum SizeMessages { SIZE_RESTORED = 0, SIZE_MINIMIZED = 1, SIZE_MAXIMIZED = 2, SIZE_MAXSHOW = 3, SIZE_MAXHIDE = 4 } public enum WinMessages : int { WM_DPICHANGED = 0x02E0, WM_GETMINMAXINFO = 0x0024, WM_SIZE = 0x0005, WM_WINDOWPOSCHANGING = 0x0046, WM_WINDOWPOSCHANGED = 0x0047, } public enum ProcessDpiAwareness { Process_DPI_Unaware = 0, Process_System_DPI_Aware = 1, Process_Per_Monitor_DPI_Aware = 2 }
我不认为这个问题是在这个代码中; 我认为这是在WPF Window
类。 我需要find解决这个问题的方法。 但是,我可能是错的。
编辑:
我有一个testing程序,其中包含一个正常的窗口,从我的DpiAwareWindow
类下降。 当屏幕分辨率发生变化时,它performance出类似的行为。 但是,作为一个testing,我改变了代码,所以窗口从Window类下降,我没有看到行为。 所以在DpiAwareWindow
代码中有一些DpiAwareWindow
东西。
如果没有太多的要求,VS 2013有人可以下载这个WPF的每个监视器DPI意识样本程序 ,build立它,看看它是否正确的时候开始与一个较低的屏幕分辨率,然后屏幕分辨率增加?
编辑2
我刚做了一些testing,发现如果我在WindowProcedureHook
方法的switch
语句中注释掉整个WinMessages.WM_GETMINMAXINFO
大小写,问题就不会发生。 此代码的目的是限制最大化的窗口的大小,所以它不会遮盖任务栏。
这个代码被添加来保持一个最大化的窗口遮蔽任务栏。 当屏幕分辨率发生变化时,似乎在返回的内容和WPF中运行的任何逻辑之间有某种相互作用。
我终于解决了这个问题。 事实证明,我需要做的是在WindowProcedureHook
方法的switch
语句中更改一行:
case WinMessages.WM_GETMINMAXINFO: // lParam has a pointer to the MINMAXINFO structure. Marshal it into managed memory. MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) ); if ( monitor != IntPtr.Zero ) { MONITORINFO monitorInfo = new MONITORINFO(); GetMonitorInfo( monitor, monitorInfo ); // Get the Monitor's working area RECT rcWorkArea = monitorInfo.rcWork; RECT rcMonitorArea = monitorInfo.rcMonitor; // Adjust the maximized size and position to fit the work area of the current monitor mmi.ptMaxPosition.x = Math.Abs( rcWorkArea.left - rcMonitorArea.left ); mmi.ptMaxPosition.y = Math.Abs( rcWorkArea.top - rcMonitorArea.top ); mmi.ptMaxSize .x = Math.Abs( rcWorkArea.right - rcWorkArea.left ); mmi.ptMaxSize .y = Math.Abs( rcWorkArea.bottom - rcWorkArea.top ); } // Copy our changes to the mmi object back to the original Marshal.StructureToPtr( mmi, lParam, true ); handled = false; // This line used to set handled to true return IntPtr.Zero;
通过这种改变,当WM_GETMINMAXINFO
消息被接收时,通常在WPF中执行的代码仍然运行,但是它使用代码所做的MINMAXINFO
对象的变化来完成它的工作。 通过此更改,WPF窗口可以正确处理分辨率更改。
编辑
事实证明,代码不再需要专门寻找屏幕分辨率或安装的显示器更改。 也就是说,不再需要SystemEvent.DisplaySettingsChanged
事件处理程序。
原来它不是一个复杂的修复。 需要将MinTrackSize点(边界)设置为辅助监视器的工作区域尺寸。
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam) { MINMAXINFO mmi = (MINMAXINFO)System.Runtime.InteropServices.Marshal.PtrToStructure(lParam, typeof(MINMAXINFO)); /* 0x0001 // center rect to monitor 0x0000 // clip rect to monitor 0x0002 // use monitor work area 0x0000 // use monitor entire area */ int MONITOR_DEFAULTTONEAREST = 0x00000002; System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (monitor != System.IntPtr.Zero) { MONITORINFO monitorInfo = new MONITORINFO(); GetMonitorInfo(monitor, monitorInfo); RECT rcWorkArea = monitorInfo.rcWork; RECT rcMonitorArea = monitorInfo.rcMonitor; // set the maximize size of the application mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left); mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top); mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left); mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top); // reset the bounds of the application to the monitor working dimensions mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; } System.Runtime.InteropServices.Marshal.StructureToPtr(mmi, lParam, true); } [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct MINMAXINFO { public POINT ptReserved; public POINT ptMaxSize; public POINT ptMaxPosition; public POINT ptMinTrackSize; public POINT ptMaxTrackSize; };