应用程序窗口总是最大化,但可以最小化.无法拖动问题与当将第二个显示器插入或拔出时将窗口从一个显示器移动到另一个显示器时如何显示窗口有关.
当第二个显示器插入程序时,当笔记本电脑拔下电源时,它会移动到笔记本电脑的显示屏上. WPF代码也正确处理此更改.也就是说,它检测到原始大小不能适合新的显示器,所以它重绘它适合.当第二个显示器插回时,它将返回到第二个显示器,并以适合该显示器的尺寸重新绘制自己.这正是我在这种情况下想要的.问题是当程序在其他配置中启动时.
当程序在没有插入第二个监视器的情况下启动时,笔记本电脑的显示屏的尺寸将被调整到适当的大小.当第二个监视器插入程序运行时,窗口移动到第二个监视器,但是它被绘制错误.由于程序最大化,它在三面包围了一个巨大的黑色边框,内容显示在笔记本电脑显示屏上的大小相同的区域.
编辑:
我刚刚完成了一些测试,WPF似乎没有正确处理从更小的分辨率到更高分辨率的分辨率变化.窗口的行为与我在笔记本电脑显示器上启动程序时的行为是一样的然后插入第二台显示器.至少它是一致的.
我发现我可以通过处理SystemEvents.DisplaySettingsChanged事件来获取第二个监视器插入或屏幕分辨率更改的通知.在我的测试中,我发现当窗口从较小的显示器移动到较大的显示器时,当窗口移动到较大的窗口时,Width,Height,ActualWidth和ActualHeight不会改变.我所能做的最好的就是获得“高度&宽度属性值与显示器工作区域匹配的值,但ActualWidth和ActualHeight属性将不会更改.
如何强制窗口对待我的问题,就像只是一个决议改变?或者,如何强制窗口将其ActualWidth和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类中.我需要找到解决这个问题的办法.但是,我可能是错的.
编辑:
我有一个测试程序,其中包含从我的DpiAwareWindow类下降的普通窗口.当屏幕分辨率变化时,它表现出类似的行为.但是,作为测试,我更改了代码,使窗口从Window类下降,我没有看到这个行为.所以DpiAwareWindow代码中的东西不行.
如果不是太多问题,VS VS的人可以下载this WPF Per Monitor DPI Aware sample program,建立&当屏幕分辨率降低,屏幕分辨率增加时,看看它是否正常工作?
编辑2
我刚刚做了一些测试,我发现如果在WindowProcedureHook方法的switch语句中注释掉整个WinMessages.WM_GETMINMAXINFO的情况,这个问题不会发生.此代码的目的是限制最大化窗口的大小,以免遮挡任务栏.
添加了此代码以保持最大化的窗口遮盖任务栏.当屏幕分辨率变化时,在WPF中返回的任何逻辑与任何逻辑正在运行中似乎都有某种交互.
解决方法
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,true ); handled = false; // This line used to set handled to true return IntPtr.Zero;
通过此更改,当接收到WM_GETMINMAXINFO消息时通常在WPF中执行的代码仍然运行,但是使用该代码创建的MINMAXINFO对象的更改才能执行其工作.通过此更改,WPF窗口正确处理分辨率更改.
编辑
事实证明,代码不再需要专门查看屏幕分辨率或安装的显示器更改.也就是说,不再需要SystemEvent.DisplaySettingsChanged事件处理程序.