diff --git a/EleCho.WpfSuite.FluentDesign/EleCho.WpfSuite.FluentDesign.csproj b/EleCho.WpfSuite.FluentDesign/EleCho.WpfSuite.FluentDesign.csproj index 7b039d2..3521cc7 100644 --- a/EleCho.WpfSuite.FluentDesign/EleCho.WpfSuite.FluentDesign.csproj +++ b/EleCho.WpfSuite.FluentDesign/EleCho.WpfSuite.FluentDesign.csproj @@ -24,10 +24,6 @@ - - - - Designer diff --git a/EleCho.WpfSuite/Controls/Border.cs b/EleCho.WpfSuite/Controls/Border.cs index 18a3333..e28cd0e 100644 --- a/EleCho.WpfSuite/Controls/Border.cs +++ b/EleCho.WpfSuite/Controls/Border.cs @@ -53,7 +53,6 @@ public Geometry ContentClip contentGeometry.Freeze(); return contentGeometry; - } else { @@ -62,10 +61,36 @@ public Geometry ContentClip } /// - protected override void OnRender(DrawingContext dc) + protected override Size ArrangeOverride(Size finalSize) { SetValue(ContentClipPropertyKey, CalculateContentClip()); - base.OnRender(dc); + + return base.ArrangeOverride(finalSize); + } + + /// + protected override Geometry GetLayoutClip(Size layoutSlotSize) + { + var borderThickness = BorderThickness; + var cornerRadius = CornerRadius; + var renderSize = RenderSize; + + if (renderSize.Width > 0 && renderSize.Height > 0) + { + var rect = new Rect(0, 0, renderSize.Width, renderSize.Height); + var radii = new Radii(cornerRadius, borderThickness, true); + + var layoutGeometry = new StreamGeometry(); + using StreamGeometryContext ctx = layoutGeometry.Open(); + GenerateGeometry(ctx, rect, radii); + + layoutGeometry.Freeze(); + return layoutGeometry; + } + else + { + return base.GetLayoutClip(layoutSlotSize); + } } /// @@ -200,10 +225,10 @@ private struct Radii { internal Radii(CornerRadius radii, Thickness borders, bool outer) { - double left = 0.5 * borders.Left; - double top = 0.5 * borders.Top; - double right = 0.5 * borders.Right; - double bottom = 0.5 * borders.Bottom; + double left = 0.5 * borders.Left; + double top = 0.5 * borders.Top; + double right = 0.5 * borders.Right; + double bottom = 0.5 * borders.Bottom; if (outer) { diff --git a/EleCho.WpfSuite/Controls/WindowContentAdapter.cs b/EleCho.WpfSuite/Controls/WindowContentAdapter.cs new file mode 100644 index 0000000..8f571f6 --- /dev/null +++ b/EleCho.WpfSuite/Controls/WindowContentAdapter.cs @@ -0,0 +1,185 @@ +// Origin code from: https://github.com/walterlv/Walterlv.Packages/blob/master/src/Themes/Walterlv.Themes.FluentDesign/Controls/ClientAreaBorder.cs +// Changes: +// - Inherit from Decorator instead of Border +// - Make 'Padding' property read-only +// - Remove cache of properties + +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Shell; + +namespace EleCho.WpfSuite +{ + /// + /// If you're using to extend the interface to non-client area, then you can add this container to the content of in order to have the internal content automatically populate the client area section. + /// Using this container eliminates the need for the various margin adaptations done by Setter/Trigger inside the style when the window state changes. + /// + public class WindowContentAdapter : Decorator + { +#pragma warning disable IDE1006 // 命名样式 +#pragma warning disable IDE0052 // 删除未读的私有成员 + private const int SM_CXFRAME = 32; + private const int SM_CYFRAME = 33; + private const int SM_CXPADDEDBORDER = 92; +#pragma warning restore IDE0052 // 删除未读的私有成员 +#pragma warning restore IDE1006 // 命名样式 + + [DllImport("User32", ExactSpelling = true)] + private static extern int GetSystemMetrics(int nIndex); + + private Window? _nowWindow; + + /// + /// Padding of the client area. + /// + public Thickness Padding => (Thickness)GetValue(PaddingPropertyKey.DependencyProperty); + + private static readonly DependencyPropertyKey PaddingPropertyKey = DependencyProperty.RegisterReadOnly( + nameof(Padding), typeof(Thickness), typeof(WindowContentAdapter), + new FrameworkPropertyMetadata(default(Thickness), FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange)); + + /// + /// DependencyProperty of . + /// + public static readonly DependencyProperty PaddingProperty = PaddingPropertyKey.DependencyProperty; + + /// + protected override void OnVisualParentChanged(DependencyObject oldParent) + { + base.OnVisualParentChanged(oldParent); + + if (_nowWindow is { } oldWindow) + { + oldWindow.StateChanged -= Window_StateChanged; + } + + var newWindow = (Window?)Window.GetWindow(this); + if (newWindow is not null) + { + newWindow.StateChanged -= Window_StateChanged; + newWindow.StateChanged += Window_StateChanged; + } + + _nowWindow = newWindow; + + UpdatePadding(_nowWindow); + } + + /// + protected override Size MeasureOverride(Size constraint) + { + var padding = Padding; + + if (Child is { } child) + { + child.Measure(new Size( + constraint.Width - padding.Left - padding.Right, + constraint.Height - padding.Top - padding.Bottom)); + + var finalSize = new Size( + child.DesiredSize.Width + padding.Left + padding.Right, + child.DesiredSize.Height + padding.Top + padding.Bottom); + + finalSize.Width = Math.Min(finalSize.Width, constraint.Width); + finalSize.Height = Math.Min(finalSize.Height, constraint.Height); + + return finalSize; + } + else + { + var finalSize = new Size(padding.Left + padding.Right, padding.Top + padding.Bottom); + + finalSize.Width = Math.Min(finalSize.Width, constraint.Width); + finalSize.Height = Math.Min(finalSize.Height, constraint.Height); + + return finalSize; + } + } + + /// + protected override Size ArrangeOverride(Size arrangeSize) + { + var padding = Padding; + if (Child is { } child) + { + child.Arrange(new Rect( + padding.Left, + padding.Top, + arrangeSize.Width - padding.Left - padding.Right, + arrangeSize.Height - padding.Top - padding.Bottom)); + } + return arrangeSize; + } + + private void UpdatePadding(Window? window) + { + if (window is null || + WindowChrome.GetWindowChrome(window) is null) + { + SetValue(PaddingPropertyKey, default(Thickness)); + return; + } + + var padding = window.WindowState switch + { + WindowState.Maximized => WindowChromeNonClientFrameThickness, + _ => default, + }; + + SetValue(PaddingPropertyKey, padding); + } + + private void Window_StateChanged(object? sender, EventArgs e) + { + if (sender is not Window window) + { + return; + } + + UpdatePadding(window); + } + + /// + /// 获取系统的 作为 WPF 单位的边框数值。 + /// + private Thickness PaddedBorderThickness + { + get + { + var paddedBorder = GetSystemMetrics(SM_CXPADDEDBORDER); + var dpi = GetDpi(); + var frameSize = new Size(paddedBorder, paddedBorder); + var frameSizeInDips = new Size(frameSize.Width / dpi.FactorX, frameSize.Height / dpi.FactorY); + return new Thickness(frameSizeInDips.Width, frameSizeInDips.Height, frameSizeInDips.Width, frameSizeInDips.Height); + } + } + + /// + /// 获取系统的 作为 WPF 单位的边框数值。 + /// + private Thickness ResizeFrameBorderThickness => new Thickness( + SystemParameters.ResizeFrameVerticalBorderWidth, + SystemParameters.ResizeFrameHorizontalBorderHeight, + SystemParameters.ResizeFrameVerticalBorderWidth, + SystemParameters.ResizeFrameHorizontalBorderHeight); + + /// + /// 如果使用了 来制作窗口样式以将窗口客户区覆盖到非客户区,那么就需要自己来处理窗口最大化后非客户区的边缘被裁切的问题。 + /// 使用此属性获取窗口最大化时窗口样式应该内缩的边距数值,这样在窗口最大化时客户区便可以在任何 DPI 下不差任何一个像素地完全覆盖屏幕工作区。 + /// 方法无法直接获得这个数值。 + /// + private Thickness WindowChromeNonClientFrameThickness => new Thickness( + ResizeFrameBorderThickness.Left + PaddedBorderThickness.Left, + ResizeFrameBorderThickness.Top + PaddedBorderThickness.Top, + ResizeFrameBorderThickness.Right + PaddedBorderThickness.Right, + ResizeFrameBorderThickness.Bottom + PaddedBorderThickness.Bottom); + + private Dpi GetDpi() => PresentationSource.FromVisual(this) is { } source + ? new Dpi( + (int)(Dpi.Standard.X * source.CompositionTarget.TransformToDevice.M11), + (int)(Dpi.Standard.Y * source.CompositionTarget.TransformToDevice.M22)) + : Dpi.System; + } +} diff --git a/EleCho.WpfSuite/Utilities/Dpi.cs b/EleCho.WpfSuite/Utilities/Dpi.cs new file mode 100644 index 0000000..a8d5dd0 --- /dev/null +++ b/EleCho.WpfSuite/Utilities/Dpi.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace EleCho.WpfSuite +{ + /// + /// DPI + /// + /// Horizontal value + /// Vertical value + public record struct Dpi(int X, int Y) + { + /// + /// Horizontal factor to standard DPI + /// + public double FactorX => (double)X / Standard.X; + + /// + /// Vertical factor to standard DPI + /// + public double FactorY => (double)Y / Standard.Y; + + /// + /// Standard DPI + /// + public static Dpi Standard => new Dpi(96, 96); + + /// + /// System DPI + /// + public static Dpi System => new Dpi(GetDeviceCaps(default, LOGPIXELSX), GetDeviceCaps(default, LOGPIXELSY)); + + + + const int LOGPIXELSX = 88; + const int LOGPIXELSY = 90; + + [DllImport("Gdi32", ExactSpelling = true)] + private static extern int GetDeviceCaps(nint hDC, int index); + } +} diff --git a/WpfTest/MainWindow.xaml b/WpfTest/MainWindow.xaml index 01292a9..b8ba4a1 100644 --- a/WpfTest/MainWindow.xaml +++ b/WpfTest/MainWindow.xaml @@ -24,28 +24,29 @@ - - + + - - - + - - - + + - - - + + - - - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - + + + + + + - - + - - + + - - - - - + + + + - - - - - + + + + - - - - - + + + + - - - - - + + + + - - - - - - + + + + + - - - - - - - - - + + + + + + + + - - - + + - - - - - - - + + + + + + - - - - + + + + diff --git a/WpfTest/Tests/TempPage.xaml b/WpfTest/Tests/TempPage.xaml index a7d43b7..28eb8b0 100644 --- a/WpfTest/Tests/TempPage.xaml +++ b/WpfTest/Tests/TempPage.xaml @@ -251,7 +251,14 @@ - + + +