maui-linux/Xamarin.Forms.Platform.WPF/Microsoft.Windows.Shell/SystemParameters2.cs

571 строка
21 KiB
C#

/**************************************************************************\
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")]
internal class SystemParameters2 : INotifyPropertyChanged
{
delegate void _SystemMetricUpdate(IntPtr wParam, IntPtr lParam);
[ThreadStatic]
static SystemParameters2 _threadLocalSingleton;
MessageWindow _messageHwnd;
bool _isGlassEnabled;
Color _glassColor;
SolidColorBrush _glassColorBrush;
Thickness _windowResizeBorderThickness;
Thickness _windowNonClientFrameThickness;
double _captionHeight;
Size _smallIconSize;
string _uxThemeName;
string _uxThemeColor;
bool _isHighContrast;
CornerRadius _windowCornerRadius;
Rect _captionButtonLocation;
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.
void _InitializeIsGlassEnabled()
{
IsGlassEnabled = NativeMethods.DwmIsCompositionEnabled();
}
void _UpdateIsGlassEnabled(IntPtr wParam, IntPtr lParam)
{
// Neither the wParam or lParam are used in this case.
_InitializeIsGlassEnabled();
}
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;
}
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;
}
void _InitializeCaptionHeight()
{
Point ptCaption = new Point(0, NativeMethods.GetSystemMetrics(SM.CYCAPTION));
WindowCaptionHeight = DpiHelper.DevicePixelsToLogical(ptCaption).Y;
}
void _UpdateCaptionHeight(IntPtr wParam, IntPtr lParam)
{
_InitializeCaptionHeight();
}
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);
}
void _UpdateWindowResizeBorderThickness(IntPtr wParam, IntPtr lParam)
{
_InitializeWindowResizeBorderThickness();
}
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);
}
void _UpdateWindowNonClientFrameThickness(IntPtr wParam, IntPtr lParam)
{
_InitializeWindowNonClientFrameThickness();
}
void _InitializeSmallIconSize()
{
SmallIconSize = new Size(
NativeMethods.GetSystemMetrics(SM.CXSMICON),
NativeMethods.GetSystemMetrics(SM.CYSMICON));
}
void _UpdateSmallIconSize(IntPtr wParam, IntPtr lParam)
{
_InitializeSmallIconSize();
}
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")]
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.
// use SW.SHOWNA instead SW.SHOW to avoid some brief flashing when launched the window
NativeMethods.ShowWindow(_messageHwnd.Handle, SW.SHOWNA);
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;
}
void _UpdateCaptionButtonLocation(IntPtr wParam, IntPtr lParam)
{
_InitializeCaptionButtonLocation();
}
void _InitializeHighContrast()
{
HIGHCONTRAST hc = NativeMethods.SystemParameterInfo_GetHIGHCONTRAST();
HighContrast = (hc.dwFlags & HCF.HIGHCONTRASTON) != 0;
}
void _UpdateHighContrast(IntPtr wParam, IntPtr lParam)
{
_InitializeHighContrast();
}
void _InitializeThemeInfo()
{
if (!NativeMethods.IsThemeActive())
{
UxThemeName = "Classic";
UxThemeColor = "";
return;
}
try
{
// wrap GetCurrentThemeName in a try/catch as we were seeing an exception
// even though the theme service seemed to be active (UxTheme IsThemeActive)
// see http://stackoverflow.com/questions/8893854/wpf-application-ribbon-crash as an example.
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;
}
catch (Exception)
{
UxThemeName = "Classic";
UxThemeColor = "";
}
}
void _UpdateThemeInfo(IntPtr wParam, IntPtr lParam)
{
_InitializeThemeInfo();
}
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;
}
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>
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;
}
}
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
void _NotifyPropertyChanged(string propertyName)
{
Assert.IsNeitherNullNorEmpty(propertyName);
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}