using WPFluent.Appearance; using WPFluent.Hardware; using System.Windows.Shell; using Size = System.Windows.Size; // ReSharper disable once CheckNamespace namespace WPFluent.Controls; /// /// If you use to extend the UI elements to the non-client area, you can include this /// container in the template of so that the content inside automatically fills the client area. /// Using this container can let you get rid of various margin adaptations done in Setter/Trigger of the style of when the window state changes. /// /// /// <Style x:Key="MyWindowCustomStyle" BasedOn="{StaticResource {x:Type Window}}" /// TargetType="{x:Type controls:FluentWindow}"> <Setter Property="Template" > <Setter.Value> /// <ControlTemplate TargetType="{x:Type Window}"> <AdornerDecorator> <controls:ClientAreaBorder /// Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" /// BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter x:Name="ContentPresenter" /> /// </controls:ClientAreaBorder> </AdornerDecorator> </ControlTemplate> </Setter.Value> /// </Setter> </Style> /// public class ClientAreaBorder : System.Windows.Controls.Border, IThemeControl { /*private const int SM_CXFRAME = 32; private const int SM_CYFRAME = 33; private const int SM_CXPADDEDBORDER = 92;*/ private static Thickness? _paddedBorderThickness; private static Thickness? _resizeFrameBorderThickness; private static Thickness? _windowChromeNonClientFrameThickness; private bool _borderBrushApplied = false; private System.Windows.Window? _oldWindow; public ClientAreaBorder() { ApplicationTheme = Appearance.ApplicationThemeManager.GetAppTheme(); Appearance.ApplicationThemeManager.Changed += OnThemeChanged; } private void ApplyDefaultWindowBorder() { if(Win32.Utilities.IsOSWindows11OrNewer || _oldWindow == null) { return; } _borderBrushApplied = true; // SystemParameters.WindowGlassBrush Color borderColor = ApplicationTheme == ApplicationTheme.Light ? Color.FromArgb(0xFF, 0x7A, 0x7A, 0x7A) : Color.FromArgb(0xFF, 0x3A, 0x3A, 0x3A); _oldWindow.SetCurrentValue( System.Windows.Controls.Control.BorderBrushProperty, new SolidColorBrush(borderColor)); _oldWindow.SetCurrentValue(System.Windows.Controls.Control.BorderThicknessProperty, new Thickness(1)); } private (double FactorX, double FactorY) GetDpi() { if(PresentationSource.FromVisual(this) is { } source) { return ( source.CompositionTarget.TransformToDevice.M11, // Possible null reference source.CompositionTarget.TransformToDevice.M22 ); } DisplayDpi systemDPi = DpiHelper.GetSystemDpi(); return (systemDPi.DpiScaleX, systemDPi.DpiScaleY); } private void OnThemeChanged(ApplicationTheme currentApplicationTheme, Color systemAccent) { ApplicationTheme = currentApplicationTheme; if(!_borderBrushApplied || _oldWindow == null) { return; } ApplyDefaultWindowBorder(); } private void OnWindowClosing(object? sender, CancelEventArgs e) { Appearance.ApplicationThemeManager.Changed -= OnThemeChanged; if(_oldWindow != null) { _oldWindow.Closing -= OnWindowClosing; } } private void OnWindowStateChanged(object? sender, EventArgs e) { if(sender is not System.Windows.Window window) { return; } Thickness padding = window.WindowState == WindowState.Maximized ? WindowChromeNonClientFrameThickness : default; SetCurrentValue(PaddingProperty, padding); } /// protected override void OnVisualParentChanged(DependencyObject oldParent) { base.OnVisualParentChanged(oldParent); if(_oldWindow is { } oldWindow) { oldWindow.StateChanged -= OnWindowStateChanged; oldWindow.Closing -= OnWindowClosing; } var newWindow = System.Windows.Window.GetWindow(this); if(newWindow is not null) { newWindow.StateChanged -= OnWindowStateChanged; // Unsafe newWindow.StateChanged += OnWindowStateChanged; newWindow.Closing += OnWindowClosing; } _oldWindow = newWindow; ApplyDefaultWindowBorder(); } public ApplicationTheme ApplicationTheme { get; set; } = ApplicationTheme.Unknown; /// /// Gets the system value for the padded border thickness () in WPF units. /// public Thickness PaddedBorderThickness { get { if(_paddedBorderThickness is not null) { return _paddedBorderThickness.Value; } var paddedBorder = Interop.User32.GetSystemMetrics(Interop.User32.SM.CXPADDEDBORDER); (double factorX, double factorY) = GetDpi(); var frameSize = new Size(paddedBorder, paddedBorder); var frameSizeInDips = new Size(frameSize.Width / factorX, frameSize.Height / factorY); _paddedBorderThickness = new Thickness( frameSizeInDips.Width, frameSizeInDips.Height, frameSizeInDips.Width, frameSizeInDips.Height); return _paddedBorderThickness.Value; } } /// /// Gets the system and values in WPF units. /// public static Thickness ResizeFrameBorderThickness => _resizeFrameBorderThickness ??= new Thickness( SystemParameters.ResizeFrameVerticalBorderWidth, SystemParameters.ResizeFrameHorizontalBorderHeight, SystemParameters.ResizeFrameVerticalBorderWidth, SystemParameters.ResizeFrameHorizontalBorderHeight); /// /// Gets the thickness of the window's non-client frame used for maximizing the window with a custom chrome. /// /// /// If you use a to extend the client area of a window to the non-client area, you need /// to handle the edge margin issue when the window is maximized. Use this property to get the correct margin value /// when the window is maximized, so that when the window is maximized, the client area can completely cover the /// screen client area by no less than a single pixel at any DPI. The method /// cannot obtain this value directly. /// public Thickness WindowChromeNonClientFrameThickness => _windowChromeNonClientFrameThickness ??= new Thickness( ClientAreaBorder.ResizeFrameBorderThickness.Left + PaddedBorderThickness.Left, ClientAreaBorder.ResizeFrameBorderThickness.Top + PaddedBorderThickness.Top, ClientAreaBorder.ResizeFrameBorderThickness.Right + PaddedBorderThickness.Right, ClientAreaBorder.ResizeFrameBorderThickness.Bottom + PaddedBorderThickness.Bottom); }