You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
607 lines
20 KiB
607 lines
20 KiB
/*************************************************************************************
|
|
|
|
Toolkit for WPF
|
|
|
|
Copyright (C) 2007-2020 Xceed Software Inc.
|
|
|
|
This program is provided to you under the terms of the XCEED SOFTWARE, INC.
|
|
COMMUNITY LICENSE AGREEMENT (for non-commercial use) as published at
|
|
https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md
|
|
|
|
For more features, controls, and fast professional support,
|
|
pick up the Plus Edition at https://xceed.com/xceed-toolkit-plus-for-wpf/
|
|
|
|
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<WM, List<_SystemMetricUpdate>> _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
|
|
|
|
/// <summary>
|
|
/// Private constructor. The public way to access this class is through the static Current property.
|
|
/// </summary>
|
|
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, List<_SystemMetricUpdate>>
|
|
{
|
|
{ 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
|
|
}
|
|
}
|
|
|