/************************************************************************************* Extended WPF Toolkit Copyright (C) 2007-2013 Xceed Software Inc. This program is provided to you under the terms of the Microsoft Public License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license For more features, controls, and fast professional support, pick up the Plus Edition at http://xceed.com/wpf_toolkit Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids ***********************************************************************************/ /**************************************************************************\ Copyright Microsoft Corporation. All Rights Reserved. \**************************************************************************/ namespace Microsoft.Windows.Shell { using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Media; using Standard; [SuppressMessage( "Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable" )] public class SystemParameters2 : INotifyPropertyChanged { private delegate void _SystemMetricUpdate( IntPtr wParam, IntPtr lParam ); [ThreadStatic] private static SystemParameters2 _threadLocalSingleton; private MessageWindow _messageHwnd; private bool _isGlassEnabled; private Color _glassColor; private SolidColorBrush _glassColorBrush; private Thickness _windowResizeBorderThickness; private Thickness _windowNonClientFrameThickness; private double _captionHeight; private Size _smallIconSize; private string _uxThemeName; private string _uxThemeColor; private bool _isHighContrast; private CornerRadius _windowCornerRadius; private Rect _captionButtonLocation; private readonly Dictionary> _UpdateTable; #region Initialization and Update Methods // Most properties exposed here have a way of being queried directly // and a way of being notified of updates via a window message. // This region is a grouping of both, for each of the exposed properties. private void _InitializeIsGlassEnabled() { IsGlassEnabled = NativeMethods.DwmIsCompositionEnabled(); } private void _UpdateIsGlassEnabled( IntPtr wParam, IntPtr lParam ) { // Neither the wParam or lParam are used in this case. _InitializeIsGlassEnabled(); } private void _InitializeGlassColor() { bool isOpaque; uint color; NativeMethods.DwmGetColorizationColor( out color, out isOpaque ); color |= isOpaque ? 0xFF000000 : 0; WindowGlassColor = Utility.ColorFromArgbDword( color ); var glassBrush = new SolidColorBrush( WindowGlassColor ); glassBrush.Freeze(); WindowGlassBrush = glassBrush; } private void _UpdateGlassColor( IntPtr wParam, IntPtr lParam ) { bool isOpaque = lParam != IntPtr.Zero; uint color = unchecked(( uint )( int )wParam.ToInt64()); color |= isOpaque ? 0xFF000000 : 0; WindowGlassColor = Utility.ColorFromArgbDword( color ); var glassBrush = new SolidColorBrush( WindowGlassColor ); glassBrush.Freeze(); WindowGlassBrush = glassBrush; } private void _InitializeCaptionHeight() { Point ptCaption = new Point( 0, NativeMethods.GetSystemMetrics( SM.CYCAPTION ) ); WindowCaptionHeight = DpiHelper.DevicePixelsToLogical( ptCaption ).Y; } private void _UpdateCaptionHeight( IntPtr wParam, IntPtr lParam ) { _InitializeCaptionHeight(); } private void _InitializeWindowResizeBorderThickness() { Size frameSize = new Size( NativeMethods.GetSystemMetrics( SM.CXSIZEFRAME ), NativeMethods.GetSystemMetrics( SM.CYSIZEFRAME ) ); Size frameSizeInDips = DpiHelper.DeviceSizeToLogical( frameSize ); WindowResizeBorderThickness = new Thickness( frameSizeInDips.Width, frameSizeInDips.Height, frameSizeInDips.Width, frameSizeInDips.Height ); } private void _UpdateWindowResizeBorderThickness( IntPtr wParam, IntPtr lParam ) { _InitializeWindowResizeBorderThickness(); } private void _InitializeWindowNonClientFrameThickness() { Size frameSize = new Size( NativeMethods.GetSystemMetrics( SM.CXSIZEFRAME ), NativeMethods.GetSystemMetrics( SM.CYSIZEFRAME ) ); Size frameSizeInDips = DpiHelper.DeviceSizeToLogical( frameSize ); int captionHeight = NativeMethods.GetSystemMetrics( SM.CYCAPTION ); double captionHeightInDips = DpiHelper.DevicePixelsToLogical( new Point( 0, captionHeight ) ).Y; WindowNonClientFrameThickness = new Thickness( frameSizeInDips.Width, frameSizeInDips.Height + captionHeightInDips, frameSizeInDips.Width, frameSizeInDips.Height ); } private void _UpdateWindowNonClientFrameThickness( IntPtr wParam, IntPtr lParam ) { _InitializeWindowNonClientFrameThickness(); } private void _InitializeSmallIconSize() { SmallIconSize = new Size( NativeMethods.GetSystemMetrics( SM.CXSMICON ), NativeMethods.GetSystemMetrics( SM.CYSMICON ) ); } private void _UpdateSmallIconSize( IntPtr wParam, IntPtr lParam ) { _InitializeSmallIconSize(); } private void _LegacyInitializeCaptionButtonLocation() { // This calculation isn't quite right, but it's pretty close. // I expect this is good enough for the scenarios where this is expected to be used. int captionX = NativeMethods.GetSystemMetrics( SM.CXSIZE ); int captionY = NativeMethods.GetSystemMetrics( SM.CYSIZE ); int frameX = NativeMethods.GetSystemMetrics( SM.CXSIZEFRAME ) + NativeMethods.GetSystemMetrics( SM.CXEDGE ); int frameY = NativeMethods.GetSystemMetrics( SM.CYSIZEFRAME ) + NativeMethods.GetSystemMetrics( SM.CYEDGE ); Rect captionRect = new Rect( 0, 0, captionX * 3, captionY ); captionRect.Offset( -frameX - captionRect.Width, frameY ); WindowCaptionButtonsLocation = captionRect; } [SuppressMessage( "Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands" )] private void _InitializeCaptionButtonLocation() { // There is a completely different way to do this on XP. if( !Utility.IsOSVistaOrNewer || !NativeMethods.IsThemeActive() ) { _LegacyInitializeCaptionButtonLocation(); return; } var tbix = new TITLEBARINFOEX { cbSize = Marshal.SizeOf( typeof( TITLEBARINFOEX ) ) }; IntPtr lParam = Marshal.AllocHGlobal( tbix.cbSize ); try { Marshal.StructureToPtr( tbix, lParam, false ); // This might flash a window in the taskbar while being calculated. // WM_GETTITLEBARINFOEX doesn't work correctly unless the window is visible while processing. NativeMethods.ShowWindow( _messageHwnd.Handle, SW.SHOW ); NativeMethods.SendMessage( _messageHwnd.Handle, WM.GETTITLEBARINFOEX, IntPtr.Zero, lParam ); tbix = ( TITLEBARINFOEX )Marshal.PtrToStructure( lParam, typeof( TITLEBARINFOEX ) ); } finally { NativeMethods.ShowWindow( _messageHwnd.Handle, SW.HIDE ); Utility.SafeFreeHGlobal( ref lParam ); } // TITLEBARINFOEX has information relative to the screen. We need to convert the containing rect // to instead be relative to the top-right corner of the window. RECT rcAllCaptionButtons = RECT.Union( tbix.rgrect_CloseButton, tbix.rgrect_MinimizeButton ); // For all known themes, the RECT for the maximize box shouldn't add anything to the union of the minimize and close boxes. Assert.AreEqual( rcAllCaptionButtons, RECT.Union( rcAllCaptionButtons, tbix.rgrect_MaximizeButton ) ); RECT rcWindow = NativeMethods.GetWindowRect( _messageHwnd.Handle ); // Reorient the Top/Right to be relative to the top right edge of the Window. var deviceCaptionLocation = new Rect( rcAllCaptionButtons.Left - rcWindow.Width - rcWindow.Left, rcAllCaptionButtons.Top - rcWindow.Top, rcAllCaptionButtons.Width, rcAllCaptionButtons.Height ); Rect logicalCaptionLocation = DpiHelper.DeviceRectToLogical( deviceCaptionLocation ); WindowCaptionButtonsLocation = logicalCaptionLocation; } private void _UpdateCaptionButtonLocation( IntPtr wParam, IntPtr lParam ) { _InitializeCaptionButtonLocation(); } private void _InitializeHighContrast() { HIGHCONTRAST hc = NativeMethods.SystemParameterInfo_GetHIGHCONTRAST(); HighContrast = ( hc.dwFlags & HCF.HIGHCONTRASTON ) != 0; } private void _UpdateHighContrast( IntPtr wParam, IntPtr lParam ) { _InitializeHighContrast(); } private void _InitializeThemeInfo() { if( !NativeMethods.IsThemeActive() ) { UxThemeName = "Classic"; UxThemeColor = ""; return; } string name; string color; string size; NativeMethods.GetCurrentThemeName( out name, out color, out size ); // Consider whether this is the most useful way to expose this... UxThemeName = System.IO.Path.GetFileNameWithoutExtension( name ); UxThemeColor = color; } private void _UpdateThemeInfo( IntPtr wParam, IntPtr lParam ) { _InitializeThemeInfo(); } private void _InitializeWindowCornerRadius() { // The radius of window corners isn't exposed as a true system parameter. // It instead is a logical size that we're approximating based on the current theme. // There aren't any known variations based on theme color. Assert.IsNeitherNullNorEmpty( UxThemeName ); // These radii are approximate. The way WPF does rounding is different than how // rounded-rectangle HRGNs are created, which is also different than the actual // round corners on themed Windows. For now we're not exposing anything to // mitigate the differences. var cornerRadius = default( CornerRadius ); // This list is known to be incomplete and very much not future-proof. // On XP there are at least a couple of shipped themes that this won't catch, // "Zune" and "Royale", but WPF doesn't know about these either. // If a new theme was to replace Aero, then this will fall back on "classic" behaviors. // This isn't ideal, but it's not the end of the world. WPF will generally have problems anyways. switch( UxThemeName.ToUpperInvariant() ) { case "LUNA": cornerRadius = new CornerRadius( 6, 6, 0, 0 ); break; case "AERO": // Aero has two cases. One with glass and one without... if( NativeMethods.DwmIsCompositionEnabled() ) { cornerRadius = new CornerRadius( 8 ); } else { cornerRadius = new CornerRadius( 6, 6, 0, 0 ); } break; case "CLASSIC": case "ZUNE": case "ROYALE": default: cornerRadius = new CornerRadius( 0 ); break; } WindowCornerRadius = cornerRadius; } private void _UpdateWindowCornerRadius( IntPtr wParam, IntPtr lParam ) { // Neither the wParam or lParam are used in this case. _InitializeWindowCornerRadius(); } #endregion /// /// Private constructor. The public way to access this class is through the static Current property. /// private SystemParameters2() { // This window gets used for calculations about standard caption button locations // so it has WS_OVERLAPPEDWINDOW as a style to give it normal caption buttons. // This window may be shown during calculations of caption bar information, so create it at a location that's likely offscreen. _messageHwnd = new MessageWindow( ( CS )0, WS.OVERLAPPEDWINDOW | WS.DISABLED, ( WS_EX )0, new Rect( -16000, -16000, 100, 100 ), "", _WndProc ); _messageHwnd.Dispatcher.ShutdownStarted += ( sender, e ) => Utility.SafeDispose( ref _messageHwnd ); // Fixup the default values of the DPs. _InitializeIsGlassEnabled(); _InitializeGlassColor(); _InitializeCaptionHeight(); _InitializeWindowNonClientFrameThickness(); _InitializeWindowResizeBorderThickness(); _InitializeCaptionButtonLocation(); _InitializeSmallIconSize(); _InitializeHighContrast(); _InitializeThemeInfo(); // WindowCornerRadius isn't exposed by true system parameters, so it requires the theme to be initialized first. _InitializeWindowCornerRadius(); _UpdateTable = new Dictionary> { { WM.THEMECHANGED, new List<_SystemMetricUpdate> { _UpdateThemeInfo, _UpdateHighContrast, _UpdateWindowCornerRadius, _UpdateCaptionButtonLocation, } }, { WM.SETTINGCHANGE, new List<_SystemMetricUpdate> { _UpdateCaptionHeight, _UpdateWindowResizeBorderThickness, _UpdateSmallIconSize, _UpdateHighContrast, _UpdateWindowNonClientFrameThickness, _UpdateCaptionButtonLocation, } }, { WM.DWMNCRENDERINGCHANGED, new List<_SystemMetricUpdate> { _UpdateIsGlassEnabled } }, { WM.DWMCOMPOSITIONCHANGED, new List<_SystemMetricUpdate> { _UpdateIsGlassEnabled } }, { WM.DWMCOLORIZATIONCOLORCHANGED, new List<_SystemMetricUpdate> { _UpdateGlassColor } }, }; } public static SystemParameters2 Current { get { if( _threadLocalSingleton == null ) { _threadLocalSingleton = new SystemParameters2(); } return _threadLocalSingleton; } } private IntPtr _WndProc( IntPtr hwnd, WM msg, IntPtr wParam, IntPtr lParam ) { // Don't do this if called within the SystemParameters2 constructor if( _UpdateTable != null ) { List<_SystemMetricUpdate> handlers; if( _UpdateTable.TryGetValue( msg, out handlers ) ) { Assert.IsNotNull( handlers ); foreach( var handler in handlers ) { handler( wParam, lParam ); } } } return NativeMethods.DefWindowProc( hwnd, msg, wParam, lParam ); } public bool IsGlassEnabled { get { // return _isGlassEnabled; // It turns out there may be some lag between someone asking this // and the window getting updated. It's not too expensive, just always do the check. return NativeMethods.DwmIsCompositionEnabled(); } private set { if( value != _isGlassEnabled ) { _isGlassEnabled = value; _NotifyPropertyChanged( "IsGlassEnabled" ); } } } public Color WindowGlassColor { get { return _glassColor; } private set { if( value != _glassColor ) { _glassColor = value; _NotifyPropertyChanged( "WindowGlassColor" ); } } } public SolidColorBrush WindowGlassBrush { get { return _glassColorBrush; } private set { Assert.IsNotNull( value ); Assert.IsTrue( value.IsFrozen ); if( _glassColorBrush == null || value.Color != _glassColorBrush.Color ) { _glassColorBrush = value; _NotifyPropertyChanged( "WindowGlassBrush" ); } } } public Thickness WindowResizeBorderThickness { get { return _windowResizeBorderThickness; } private set { if( value != _windowResizeBorderThickness ) { _windowResizeBorderThickness = value; _NotifyPropertyChanged( "WindowResizeBorderThickness" ); } } } public Thickness WindowNonClientFrameThickness { get { return _windowNonClientFrameThickness; } private set { if( value != _windowNonClientFrameThickness ) { _windowNonClientFrameThickness = value; _NotifyPropertyChanged( "WindowNonClientFrameThickness" ); } } } public double WindowCaptionHeight { get { return _captionHeight; } private set { if( value != _captionHeight ) { _captionHeight = value; _NotifyPropertyChanged( "WindowCaptionHeight" ); } } } public Size SmallIconSize { get { return new Size( _smallIconSize.Width, _smallIconSize.Height ); } private set { if( value != _smallIconSize ) { _smallIconSize = value; _NotifyPropertyChanged( "SmallIconSize" ); } } } [SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ux" )] [SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ux" )] public string UxThemeName { get { return _uxThemeName; } private set { if( value != _uxThemeName ) { _uxThemeName = value; _NotifyPropertyChanged( "UxThemeName" ); } } } [SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ux" )] [SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ux" )] public string UxThemeColor { get { return _uxThemeColor; } private set { if( value != _uxThemeColor ) { _uxThemeColor = value; _NotifyPropertyChanged( "UxThemeColor" ); } } } public bool HighContrast { get { return _isHighContrast; } private set { if( value != _isHighContrast ) { _isHighContrast = value; _NotifyPropertyChanged( "HighContrast" ); } } } public CornerRadius WindowCornerRadius { get { return _windowCornerRadius; } private set { if( value != _windowCornerRadius ) { _windowCornerRadius = value; _NotifyPropertyChanged( "WindowCornerRadius" ); } } } public Rect WindowCaptionButtonsLocation { get { return _captionButtonLocation; } private set { if( value != _captionButtonLocation ) { _captionButtonLocation = value; _NotifyPropertyChanged( "WindowCaptionButtonsLocation" ); } } } #region INotifyPropertyChanged Members private void _NotifyPropertyChanged( string propertyName ) { Assert.IsNeitherNullNorEmpty( propertyName ); var handler = PropertyChanged; if( handler != null ) { handler( this, new PropertyChangedEventArgs( propertyName ) ); } } public event PropertyChangedEventHandler PropertyChanged; #endregion } }