Skip to content

Commit

Permalink
Merge branch 'master' of github.com:OrgEleCho/EleCho.WpfSuite
Browse files Browse the repository at this point in the history
  • Loading branch information
SlimeNull committed Aug 29, 2024
2 parents 04fd935 + 448667a commit 7bad3dc
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@
<ProjectReference Include="..\EleCho.WpfSuite\EleCho.WpfSuite.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Controls\" />
</ItemGroup>

<ItemGroup>
<Page Update="Styles\PasswordBoxResources.xaml">
<SubType>Designer</SubType>
Expand Down
39 changes: 32 additions & 7 deletions EleCho.WpfSuite/Controls/Border.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public Geometry ContentClip

contentGeometry.Freeze();
return contentGeometry;

}
else
{
Expand All @@ -62,10 +61,36 @@ public Geometry ContentClip
}

/// <inheritdoc/>
protected override void OnRender(DrawingContext dc)
protected override Size ArrangeOverride(Size finalSize)
{
SetValue(ContentClipPropertyKey, CalculateContentClip());
base.OnRender(dc);

return base.ArrangeOverride(finalSize);
}

/// <inheritdoc/>
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);
}
}

/// <summary>
Expand Down Expand Up @@ -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)
{
Expand Down
185 changes: 185 additions & 0 deletions EleCho.WpfSuite/Controls/WindowContentAdapter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// If you're using <see cref="WindowChrome"/> to extend the interface to non-client area, then you can add this container to the content of <see cref="Window"/> 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 <see cref="Window"/> style when the window state changes.
/// </summary>
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;

/// <summary>
/// Padding of the client area.
/// </summary>
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));

/// <summary>
/// DependencyProperty of <see cref="Padding"/>.
/// </summary>
public static readonly DependencyProperty PaddingProperty = PaddingPropertyKey.DependencyProperty;

/// <inheritdoc />
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);
}

/// <inheritdoc />
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;
}
}

/// <inheritdoc />
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);
}

/// <summary>
/// 获取系统的 <see cref="SM_CXPADDEDBORDER"/> 作为 WPF 单位的边框数值。
/// </summary>
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);
}
}

/// <summary>
/// 获取系统的 <see cref="SM_CXFRAME"/> 和 <see cref="SM_CYFRAME"/> 作为 WPF 单位的边框数值。
/// </summary>
private Thickness ResizeFrameBorderThickness => new Thickness(
SystemParameters.ResizeFrameVerticalBorderWidth,
SystemParameters.ResizeFrameHorizontalBorderHeight,
SystemParameters.ResizeFrameVerticalBorderWidth,
SystemParameters.ResizeFrameHorizontalBorderHeight);

/// <summary>
/// 如果使用了 <see cref="WindowChrome"/> 来制作窗口样式以将窗口客户区覆盖到非客户区,那么就需要自己来处理窗口最大化后非客户区的边缘被裁切的问题。
/// 使用此属性获取窗口最大化时窗口样式应该内缩的边距数值,这样在窗口最大化时客户区便可以在任何 DPI 下不差任何一个像素地完全覆盖屏幕工作区。
/// <see cref="GetSystemMetrics"/> 方法无法直接获得这个数值。
/// </summary>
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;
}
}
40 changes: 40 additions & 0 deletions EleCho.WpfSuite/Utilities/Dpi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Runtime.InteropServices;

namespace EleCho.WpfSuite
{
/// <summary>
/// DPI
/// </summary>
/// <param name="X">Horizontal value</param>
/// <param name="Y">Vertical value</param>
public record struct Dpi(int X, int Y)
{
/// <summary>
/// Horizontal factor to standard DPI
/// </summary>
public double FactorX => (double)X / Standard.X;

/// <summary>
/// Vertical factor to standard DPI
/// </summary>
public double FactorY => (double)Y / Standard.Y;

/// <summary>
/// Standard DPI
/// </summary>
public static Dpi Standard => new Dpi(96, 96);

/// <summary>
/// System DPI
/// </summary>
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);
}
}
Loading

0 comments on commit 7bad3dc

Please sign in to comment.