Vanara/Windows.Forms/DesktopWindowManager.cs

481 lines
21 KiB
C#

using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Security;
using System.Windows.Forms;
using Vanara.PInvoke;
using static Vanara.PInvoke.DwmApi;
namespace Vanara.Windows.Forms;
/// <summary>Main DWM class, provides glass sheet effect and blur behind.</summary>
[SecuritySafeCritical]
public static partial class DesktopWindowManager
{
internal static readonly ThumbnailMgr thumbnailMgr = new();
private static readonly object _lock = new();
private static readonly object colorizationColorChangedKey = new();
private static readonly object compositionChangedKey = new();
private static readonly object nonClientRenderingChangedKey = new();
private static readonly object[] keys = { compositionChangedKey, nonClientRenderingChangedKey, colorizationColorChangedKey/*, WindowMaximizedChangedKey*/ };
private static EventHandlerList? eventHandlerList;
private static MessageWindow? msgWin;
/// <summary>Occurs when the colorization color has changed.</summary>
public static event EventHandler ColorizationColorChanged
{
add => AddEventHandler(colorizationColorChangedKey, value);
remove => RemoveEventHandler(colorizationColorChangedKey, value);
}
/// <summary>Occurs when the desktop window composition has been enabled or disabled.</summary>
public static event EventHandler CompositionChanged
{
add => AddEventHandler(compositionChangedKey, value);
remove => RemoveEventHandler(compositionChangedKey, value);
}
/// <summary>Occurs when the non-client area rendering policy has changed.</summary>
public static event EventHandler NonClientRenderingChanged
{
add => AddEventHandler(nonClientRenderingChangedKey, value);
remove => RemoveEventHandler(nonClientRenderingChangedKey, value);
}
/// <summary>
/// Use with GetWindowAttr and WindowAttribute.Cloaked. If the window is cloaked, provides one of the following values explaining why.
/// </summary>
[Flags]
public enum CloakingSource
{
/// <summary>The window was cloaked by its owner application.</summary>
App = 0x01,
/// <summary>The window was cloaked by the Shell.</summary>
Shell = 0x02,
/// <summary>The cloak value was inherited from its owner window.</summary>
Inherited = 0x04
}
/// <summary>Flags used by the SetWindowAttr method to specify the Flip3D window policy.</summary>
public enum Flip3DWindowPolicy
{
/// <summary>
/// Use the window's style and visibility settings to determine whether to hide or include the window in Flip3D rendering.
/// </summary>
Default,
/// <summary>Exclude the window from Flip3D and display it below the Flip3D rendering.</summary>
ExcludeBelow,
/// <summary>Exclude the window from Flip3D and display it above the Flip3D rendering.</summary>
ExcludeAbove
}
/// <summary>Flags used by the SetWindowAttr method to specify the non-client area rendering policy.</summary>
public enum NonClientRenderingPolicy
{
/// <summary>The non-client rendering area is rendered based on the window style.</summary>
UseWindowStyle,
/// <summary>The non-client area rendering is disabled; the window style is ignored.</summary>
Disabled,
/// <summary>The non-client area rendering is enabled; the window style is ignored.</summary>
Enabled
}
/// <summary>
/// Gets or sets the current color used for Desktop Window Manager (DWM) glass composition. This value is based on the current color
/// scheme and can be modified by the user.
/// </summary>
/// <value>The color of the glass composition.</value>
public static Color CompositionColor
{
get
{
if (!CompositionSupported)
return Color.Transparent;
var value = (int)Microsoft.Win32.Registry.CurrentUser.GetValue(@"Software\Microsoft\Windows\DWM\ColorizationColor", 0)!;
return Color.FromArgb(value);
}
set
{
if (!CompositionSupported)
return;
DwmpGetColorizationParameters(out var p).ThrowIfFailed();
p.clrColor = (COLORREF)value;
DwmpSetColorizationParameters(p, 1).ThrowIfFailed();
Microsoft.Win32.Registry.CurrentUser.SetValue(@"Software\Microsoft\Windows\DWM\ColorizationColor", value.ToArgb(), Microsoft.Win32.RegistryValueKind.DWord);
}
}
/// <summary>Gets or sets a value indicating whether composition (Windows Aero) is enabled.</summary>
/// <value><c>true</c> if composition is enabled; otherwise, <c>false</c>.</value>
public static bool CompositionEnabled
{
get => IsCompositionEnabled();
set { if (CompositionSupported) EnableComposition(value); }
}
/// <summary>Gets or sets a value indicating whether composition (Windows Aero) is supported.</summary>
/// <value><c>true</c> if composition is supported; otherwise, <c>false</c>.</value>
public static bool CompositionSupported => Environment.OSVersion.Version.Major >= 6;
/// <summary>
/// Gets a value notifying the Desktop Window Manager (DWM) to opt in to or out of Multimedia Class Schedule Service (MMCSS)
/// scheduling while the calling process is alive.
/// </summary>
/// <value>
/// <c>true</c> to instruct DWM to participate in MMCSS scheduling; otherwise, <c>false</c> to opt out or end participation in MMCSS scheduling.
/// </value>
public static bool MultimediaClassScheduleServiceEnabled { set => DwmEnableMMCSS(value).ThrowIfFailed(); }
/// <summary>Gets or sets a value that indicates whether the <see cref="CompositionColor"/> is transparent.</summary>
/// <value><c>true</c> if transparent; otherwise, <c>false</c>.</value>
public static bool TransparencyEnabled
{
get
{
if (!CompositionSupported)
return false;
var value = (int)Microsoft.Win32.Registry.CurrentUser.GetValue(@"Software\Microsoft\Windows\DWM\ColorizationOpaqueBlend", 1)!;
return value == 0;
}
set
{
if (!CompositionSupported)
return;
DwmpGetColorizationParameters(out var p).ThrowIfFailed();
p.fOpaque = value;
DwmpSetColorizationParameters(p, 1).ThrowIfFailed();
Microsoft.Win32.Registry.CurrentUser.SetValue(@"Software\Microsoft\Windows\DWM\ColorizationOpaqueBlend", p.fOpaque, Microsoft.Win32.RegistryValueKind.DWord);
}
}
/*/// <summary>
/// Occurs when a Desktop Window Manager (DWM) composed window is maximized. </summary>
public static event EventHandler WindowMaximizedChanged
{
add { AddEventHandler(WindowMaximizedChangedKey, value); }
remove { RemoveEventHandler(WindowMaximizedChangedKey, value); }
}*/
/// <summary>Enables content rendered in the non-client area to be visible on the frame drawn by DWM.</summary>
/// <param name="form">The form.</param>
/// <param name="allowNCPaint">
/// Set to <c>true</c> to enable content rendered in the non-client area to be visible on the frame; otherwise, <c>false</c>.
/// </param>
public static void AllowNonClientPainting(this Form form, bool allowNCPaint) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_ALLOW_NCPAINT, allowNCPaint);
/// <summary>Cloaks the window such that it is not visible to the user. The window is still composed by DWM.</summary>
/// <param name="form">The form.</param>
/// <param name="cloak">If set to <c>true</c>, cloak.</param>
public static void Cloak(this Form form, bool cloak) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, cloak);
/// <summary>Enables or forcibly disables DWM transitions.</summary>
/// <param name="form">The form.</param>
/// <param name="forceDisabled"><c>true</c> to disable transitions.</param>
public static void DisableTransitions(this Form form, bool forceDisabled) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, forceDisabled);
/// <summary>
/// Do not show peek preview for the window. The peek view shows a full-sized preview of the window when the mouse hovers over the
/// window's thumbnail in the taskbar.
/// </summary>
/// <param name="form">The form.</param>
/// <param name="disallowPeek">
/// if set to <c>true</c>, hovering the mouse pointer over the window's thumbnail dismisses peek (in case another window in the group
/// has a peek preview showing).
/// </param>
public static void DisallowPeekPreview(this Form form, bool disallowPeek) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_DISALLOW_PEEK, disallowPeek);
/// <summary>Enable the Aero "Blur Behind" effect on the whole client area. Background must be black.</summary>
/// <param name="window">The window.</param>
/// <param name="enabled"><c>true</c> to enable blur behind for this window, <c>false</c> to disable it.</param>
public static void EnableBlurBehind(this IWin32Window window, bool enabled) => EnableBlurBehind(window, null, null, enabled, false);
/// <summary>Enable the Aero "Blur Behind" effect on a specific region of a drawing area. Background must be black.</summary>
/// <param name="window">The window.</param>
/// <param name="graphics">The graphics area on which the region resides.</param>
/// <param name="region">The region within the client area to apply the blur behind.</param>
/// <param name="enabled"><c>true</c> to enable blur behind for this region, <c>false</c> to disable it.</param>
/// <param name="transitionOnMaximized">
/// <c>true</c> if the window's colorization should transition to match the maximized windows; otherwise, <c>false</c>.
/// </param>
public static void EnableBlurBehind(this IWin32Window window, Graphics? graphics, Region? region, bool enabled, bool transitionOnMaximized)
{
if (window == null)
throw new ArgumentNullException(nameof(window));
var bb = new DWM_BLURBEHIND(enabled);
if (graphics != null && region != null)
{
bb.hRgnBlur = region.GetHrgn(graphics);
bb.dwFlags |= DWM_BLURBEHIND_Mask.DWM_BB_BLURREGION;
}
if (transitionOnMaximized)
bb.TransitionOnMaximized = true;
DwmEnableBlurBehindWindow(window.Handle, bb);
}
/// <summary>Enables or disables Desktop Window Manager (DWM) composition.</summary>
/// <param name="value"><c>true</c> to enable DWM composition; <c>false</c> to disable composition.</param>
public static void EnableComposition(bool value) => DwmEnableComposition(value);
/// <summary>Excludes the specified child control from the glass effect.</summary>
/// <param name="parent">The parent control.</param>
/// <param name="control">The control to exclude.</param>
/// <exception cref="ArgumentNullException">Occurs if control is null.</exception>
/// <exception cref="ArgumentException">Occurs if control is not a child control.</exception>
public static void ExcludeChildFromGlass(this Control parent, Control control)
{
if (parent == null)
throw new ArgumentNullException(nameof(parent));
if (control == null)
throw new ArgumentNullException(nameof(control));
if (!parent.Contains(control))
throw new ArgumentException("Control must be a child control.");
if (IsCompositionEnabled())
{
var clientScreen = parent.RectangleToScreen(parent.ClientRectangle);
var controlScreen = control.RectangleToScreen(control.ClientRectangle);
var margins = new MARGINS(controlScreen.Left - clientScreen.Left, controlScreen.Top - clientScreen.Top,
clientScreen.Right - controlScreen.Right, clientScreen.Bottom - controlScreen.Bottom);
// Extend the Frame into client area
DwmExtendFrameIntoClientArea(parent.Handle, margins);
}
}
/// <summary>Extends the window frame beyond the client area.</summary>
/// <param name="window">The window.</param>
/// <param name="padding">The padding to use as the area into which the frame is extended.</param>
public static void ExtendFrameIntoClientArea(this IWin32Window window, Padding padding)
{
if (window == null)
throw new ArgumentNullException(nameof(window));
var m = new MARGINS(padding.Left, padding.Right, padding.Top, padding.Bottom);
DwmExtendFrameIntoClientArea(window.Handle, m);
}
/// <summary>
/// Issues a flush call that blocks the caller until the next present, when all of the Microsoft DirectX surface updates that are
/// currently outstanding have been made. This compensates for very complex scenes or calling processes with very low priority.
/// </summary>
public static void Flush() => DwmFlush().ThrowIfFailed();
/// <summary>
/// Forces the window to display an iconic thumbnail or peek representation (a static bitmap), even if a live or snapshot
/// representation of the window is available. This value normally is set during a window's creation and not changed throughout the
/// window's lifetime. Some scenarios, however, might require the value to change over time.
/// </summary>
/// <param name="form">The form.</param>
/// <param name="iconRep"><c>true</c> to require a iconic thumbnail or peek representation; otherwise, <c>false</c>.</param>
public static void ForceIconicRepresentation(this Form form, bool iconRep) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_FORCE_ICONIC_REPRESENTATION, iconRep);
/// <summary>
/// Freeze the window's thumbnail image with its current visuals. Do no further live updates on the thumbnail image to match the
/// window's contents.
/// </summary>
/// <param name="form">The form.</param>
/// <param name="freeze">if set to <c>true</c> freeze thumbnail.</param>
public static void FreezeLiveThumbnail(this Form form, bool freeze) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_FREEZE_REPRESENTATION, freeze);
/// <summary>Retrieves the bounds of the caption button area in the window-relative space.</summary>
/// <param name="form">The form.</param>
/// <returns>The bounds.</returns>
public static Rectangle GetCaptionButtonBounds(this Form form) => GetWindowAttribute<RECT>(form, DWMWINDOWATTRIBUTE.DWMWA_CAPTION_BUTTON_BOUNDS);
/// <summary>If the window is cloaked, provides a value explaining why.</summary>
/// <param name="form">The form.</param>
/// <returns>The reason the window is cloaked.</returns>
public static CloakingSource GetCloakingSource(this Form form) => (CloakingSource)GetWindowAttribute<DWM_CLOAKED>(form, DWMWINDOWATTRIBUTE.DWMWA_CLOAKED);
/// <summary>Retrieves the extended frame bounds rectangle in screen space.</summary>
/// <param name="form">The form.</param>
/// <returns>The bounds.</returns>
public static Rectangle GetExtendedFrameBounds(this Form form) => GetWindowAttribute<RECT>(form, DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS);
/// <summary>Gets the live client thumbnail.</summary>
/// <param name="window">The window.</param>
/// <returns></returns>
public static LiveThumbnail GetLiveClientThumbnail(this IWin32Window window) => new(window);
/// <summary>
/// The window will provide a bitmap for use by DWM as an iconic thumbnail or peek representation (a static bitmap) for the window.
/// DWMWA_HAS_ICONIC_BITMAP can be specified with DWMWA_FORCE_ICONIC_REPRESENTATION. DWMWA_HAS_ICONIC_BITMAP normally is set during a
/// window's creation and not changed throughout the window's lifetime. Some scenarios, however, might require the value to change
/// over time.
/// </summary>
/// <param name="form">The form.</param>
/// <param name="hasIcon">if set to <c>true</c> inform DWM that the window will provide an iconic thumbnail or peek representation.</param>
public static void HasIconicBitmap(this Form form, bool hasIcon) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_HAS_ICONIC_BITMAP, hasIcon);
/// <summary>
/// Called by an application to indicate that all previously provided iconic bitmaps from a window, both thumbnails and peek
/// representations, should be refreshed.
/// </summary>
/// <param name="window">
/// The window or tab whose bitmaps are being invalidated through this call. This window must belong to the calling process.
/// </param>
public static void InvalidateIconicBitmaps(this IWin32Window window) => DwmInvalidateIconicBitmaps(window.Handle).ThrowIfFailed();
/// <summary>Discovers whether non-client rendering is enabled.</summary>
/// <param name="form">The form.</param>
/// <returns><c>true</c> if non client rendering is enabled; otherwise, <c>false</c>.</returns>
public static bool IsNonClientRenderingEnabled(this Form form) => GetWindowAttribute<bool>(form, DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_ENABLED);
/// <summary>Specifies whether non-client content is right-to-left (RTL) mirrored.</summary>
/// <param name="form">The form.</param>
/// <param name="rtl">if set to <c>true</c> the non-client content is right-to-left (RTL) mirrored.</param>
public static void NonClientRightToLeft(this Form form, bool rtl) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_NONCLIENT_RTL_LAYOUT, rtl);
/// <summary>Prevents a window from fading to a glass sheet when peek is invoked.</summary>
/// <param name="form">The form.</param>
/// <param name="prevent">if set to <c>true</c> prevent the window from fading during another window's peek.</param>
public static void PreventFadingOnPeekPreview(this Form form, bool prevent) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_EXCLUDED_FROM_PEEK, prevent);
/// <summary>Sets how Flip3D treats the window.</summary>
/// <param name="form">The form.</param>
/// <param name="policy">The policy.</param>
public static void SetFlip3DPolicy(this Form form, Flip3DWindowPolicy policy) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_FLIP3D_POLICY, (DWMFLIP3DWINDOWPOLICY)policy);
/// <summary>Sets the non-client rendering policy.</summary>
/// <param name="form">The form.</param>
/// <param name="policy">The policy.</param>
public static void SetNonClientRenderingPolicy(this Form form, NonClientRenderingPolicy policy) => SetWindowAttribute(form, DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, (DWMNCRENDERINGPOLICY)policy);
private static void AddEventHandler(object id, EventHandler value)
{
lock (_lock)
{
msgWin ??= new MessageWindow();
eventHandlerList ??= new EventHandlerList();
eventHandlerList.AddHandler(id, value);
}
}
/// <summary>Gets the specified window attribute from the Desktop Window Manager (DWM).</summary>
/// <typeparam name="T">Return type. Must match the attribute.</typeparam>
/// <param name="window">The window.</param>
/// <param name="attribute">The attribute.</param>
/// <returns>Value of the windows attribute.</returns>
private static T GetWindowAttribute<T>(this IWin32Window window, DWMWINDOWATTRIBUTE attribute) where T : struct
{
if (window == null)
throw new ArgumentNullException(nameof(window));
using var ptr = SafeCoTaskMemHandle.CreateFromStructure<T>();
DwmGetWindowAttribute(window.Handle, attribute, (IntPtr)ptr, ptr.Size);
return ptr.ToStructure<T>();
}
/// <summary>Indicates whether Desktop Window Manager (DWM) composition is enabled.</summary>
/// <returns><c>true</c> if is composition enabled; otherwise, <c>false</c>.</returns>
private static bool IsCompositionEnabled()
{
if (!CompositionSupported || !System.IO.File.Exists(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"dwmapi.dll")))
return false;
DwmIsCompositionEnabled(out var res);
return res;
}
private static void RemoveEventHandler(object id, EventHandler value)
{
lock (_lock)
{
eventHandlerList?.RemoveHandler(id, value);
}
}
/// <summary>Sets the specified window attribute through the Desktop Window Manager (DWM).</summary>
/// <param name="window">The window.</param>
/// <param name="attribute">The attribute.</param>
/// <param name="value">The value.</param>
private static void SetWindowAttribute<T>(this IWin32Window window, DWMWINDOWATTRIBUTE attribute, T value) where T : struct
{
if (window == null)
throw new ArgumentNullException(nameof(window));
using var ptr = SafeCoTaskMemHandle.CreateFromStructure(value);
DwmSetWindowAttribute(window.Handle, attribute, (IntPtr)ptr, ptr.Size);
}
internal class ThumbnailMgr : IDisposable
{
private readonly Dictionary<IntPtr, HTHUMBNAIL> thumbnails = new();
public ThumbnailMgr()
{
}
public void Dispose()
{
foreach (var hThumb in thumbnails.Values)
DwmUnregisterThumbnail(hThumb);
GC.SuppressFinalize(this);
}
public HTHUMBNAIL Register(IWin32Window win)
{
if (thumbnails.TryGetValue(win.Handle, out var hThumbnail))
{
DwmUnregisterThumbnail(hThumbnail);
thumbnails.Remove(win.Handle);
}
DwmRegisterThumbnail(win.Handle, User32.FindWindow("Progman", null), out var hThumb).ThrowIfFailed();
thumbnails.Add(win.Handle, hThumb);
return hThumb;
}
public void Unregister(HTHUMBNAIL hThumbnail)
{
foreach (var kv in thumbnails)
{
if (kv.Value == hThumbnail)
{
DwmUnregisterThumbnail(hThumbnail);
thumbnails.Remove(kv.Key);
break;
}
}
}
}
[SecuritySafeCritical]
private class MessageWindow : NativeWindow, IDisposable
{
public MessageWindow()
{
var cp = new CreateParams { Style = 0, ExStyle = 0, ClassStyle = 0, Parent = IntPtr.Zero, Caption = GetType().Name };
CreateHandle(cp);
}
public void Dispose() => DestroyHandle();
protected override void WndProc(ref Message m)
{
// ReSharper disable InconsistentNaming
const int WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
//const int WM_DWMNCRENDERINGCHANGED = 0x031F;
//const int WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321;
// ReSharper restore InconsistentNaming
if (m.Msg is >= WM_DWMCOMPOSITIONCHANGED and <= WM_DWMCOLORIZATIONCOLORCHANGED)
ExecuteEvents(m.Msg - WM_DWMCOMPOSITIONCHANGED);
base.WndProc(ref m);
}
private static void ExecuteEvents(int idx)
{
if (eventHandlerList == null) return;
lock (_lock)
{
try { ((EventHandler)eventHandlerList[keys[idx]]!).Invoke(null, EventArgs.Empty); }
catch { };
}
}
}
}