Files
DeedyDesigner/DeedyDesigner/Deedy.Wpf/DrawerViewer.cs
2025-09-30 18:42:49 +08:00

215 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace Deedy
{
/// <summary>
/// 抽屉窗口装饰器
/// </summary>
public class DrawerViewer : Adorner
{
private readonly VisualCollection _visualChildren;
private readonly Border _border;
public Brush Background { get => (Brush)GetValue(BackgroundProperty); set => SetValue(BackgroundProperty, value); }
public static readonly DependencyProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(typeof(DrawerViewer));
public Brush BorderBrush { get => (Brush)GetValue(BorderBrushProperty); set => SetValue(BorderBrushProperty, value); }
public static readonly DependencyProperty BorderBrushProperty = Border.BorderBrushProperty.AddOwner(typeof(DrawerViewer));
public Thickness BorderThickness { get => (Thickness)GetValue(BorderThicknessProperty); set => SetValue(BorderThicknessProperty, value); }
public static readonly DependencyProperty BorderThicknessProperty = Border.BorderThicknessProperty.AddOwner(typeof(DrawerViewer));
public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); }
public static readonly DependencyProperty CornerRadiusProperty = Border.CornerRadiusProperty.AddOwner(typeof(DrawerViewer));
private readonly ContentPresenter _contentPresenter;
public object Content { get => GetValue(ContentProperty); set => SetValue(ContentProperty, value); }
public static readonly DependencyProperty ContentProperty = ContentPresenter.ContentProperty.AddOwner(typeof(DrawerViewer));
public DataTemplate ContentTemplate { get => (DataTemplate)GetValue(ContentTemplateProperty); set => SetValue(ContentTemplateProperty, value); }
public static readonly DependencyProperty ContentTemplateProperty = ContentPresenter.ContentTemplateProperty.AddOwner(typeof(DrawerViewer));
public DataTemplateSelector ContentTemplateSelector { get => (DataTemplateSelector)GetValue(ContentTemplateSelectorProperty); set => SetValue(ContentTemplateSelectorProperty, value); }
public static readonly DependencyProperty ContentTemplateSelectorProperty = ContentPresenter.ContentTemplateSelectorProperty.AddOwner(typeof(DrawerViewer));
/// <summary>
/// 动画方向如果为「null」则使用缩放动画
/// </summary>
private readonly Dock? _direction;
/// <summary>
/// 根据抽屉加载方向创建边距动画所需要的「Thickness」对象
/// </summary>
/// <returns>动画需要的「Thickness」对象</returns>
private Thickness GetMargin4Animation()
{
switch (this._direction)
{
case Dock.Left: return new Thickness(0, 0, this.AdornedElement.RenderSize.Width, 0);
case Dock.Right: return new Thickness(this.AdornedElement.RenderSize.Width, 0, 0, 0);
case Dock.Top: return new Thickness(0, 0, 0, this.AdornedElement.RenderSize.Height);
case Dock.Bottom: return new Thickness(0, AdornedElement.RenderSize.Height, 0, 0);
default:
double horMargin = this.AdornedElement.RenderSize.Width / 2;
double verMargin = this.AdornedElement.RenderSize.Height / 2;
return new Thickness(horMargin, verMargin, horMargin, verMargin);
}
}
/// <summary>
/// 注册附属视图;注册前请确保布局属性被正确设置
/// </summary>
/// <param name="attachedView">要注册的附属视图;注册前请确保布局属性被正确设置</param>
/// <param name="dock">在注册的方向</param>
/// <returns>当前装饰器对象,方便连续操作;不会创建新的装饰器</returns>
public DrawerViewer RegisterAttachedView(UIElement attachedView, Dock? dock)
{
dock ??= (Dock)(((int)(this._direction ?? Dock.Left) + 2) % 4);
return this;
}
/// <summary>
/// 在特定「UIElement」上创建「WindowDrawer」蒙层
/// </summary>
/// <param name="adornedElement">目标「UIElement」元素</param>
/// <param name="direction">抽屉的加载方向,如果不设置则使用缩放动画</param>
public DrawerViewer(UIElement adornedElement, Dock? direction = null) : base(adornedElement)
{
this.Opacity = 0;
this._direction = direction;
_visualChildren = new VisualCollection(this);
// 创建边框并绑定属性
_border = new Border() { };
var backgroundBinding = new System.Windows.Data.Binding(BackgroundProperty.Name) { Source = this };
var borderBrushBinding = new System.Windows.Data.Binding(BorderBrushProperty.Name) { Source = this };
var borderThicknessBinding = new System.Windows.Data.Binding(BorderThicknessProperty.Name) { Source = this };
var cornerRadiusBinding = new System.Windows.Data.Binding(CornerRadiusProperty.Name) { Source = this };
_border.SetBinding(Border.BackgroundProperty, backgroundBinding);
_border.SetBinding(Border.BorderBrushProperty, borderBrushBinding);
_border.SetBinding(Border.BorderThicknessProperty, borderThicknessBinding);
_border.SetBinding(Border.CornerRadiusProperty, cornerRadiusBinding);
_visualChildren.Add(_border);
// 创建内容并绑定属性
_contentPresenter = new ContentPresenter
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
var contentBinding = new System.Windows.Data.Binding(ContentProperty.Name) { Source = this };
var contentTemplateBinding = new System.Windows.Data.Binding(ContentTemplateProperty.Name) { Source = this };
var contentTemplateSelectorBinding = new System.Windows.Data.Binding(ContentTemplateSelectorProperty.Name) { Source = this };
_contentPresenter.SetBinding(ContentPresenter.ContentProperty, contentBinding);
_contentPresenter.SetBinding(ContentPresenter.ContentTemplateProperty, contentTemplateBinding);
_contentPresenter.SetBinding(ContentPresenter.ContentTemplateSelectorProperty, contentTemplateSelectorBinding);
_border.Child = _contentPresenter;
}
[AllowNull]
private readonly RoutedEvent[] _routedEvents;
private readonly RoutedEventHandler? _routedEventHandler;
/// <summary>
/// 在特定「UIElement」上创建「WindowDrawer」蒙层同时绑定特定路由事件
/// </summary>
/// <param name="adornedElement">目标「UIElement」元素</param>
/// <param name="routedEventHandler">路由事件处理程序</param>
/// <param name="direction">抽屉的加载方向,如果不设置则使用缩放动画</param>
/// <param name="routedEvents">要绑定的路由事件</param>
/// <remarks>
/// 如果不设置「routedEvents」参数则注册「ButtonBase.ClickEvent」事件
/// </remarks>
public DrawerViewer(UIElement adornedElement, RoutedEventHandler routedEventHandler, Dock? direction, params RoutedEvent[] routedEvents)
: this(adornedElement, direction)
{
if (routedEvents.Length == 0) routedEvents = [ButtonBase.ClickEvent];
if (routedEventHandler != null)
{
_routedEvents = routedEvents;
_routedEventHandler = routedEventHandler;
foreach (var routedEvent in routedEvents)
this.AddHandler(routedEvent, routedEventHandler);
}
}
/// <summary>
/// 在特定「UIElement」上创建「WindowDrawer」蒙层同时绑定特定路由事件
/// </summary>
/// <param name="adornedElement">目标「UIElement」元素</param>
/// <param name="routedEventHandler">路由事件处理程序</param>
/// <param name="routedEvents">要绑定的路由事件</param>
/// <remarks>
/// <para>如果不设置「routedEvents」参数则注册「ButtonBase.ClickEvent」事件</para>
/// <para>抽屉采用缩放方式加载</para>
/// </remarks>
public DrawerViewer(UIElement adornedElement, RoutedEventHandler routedEventHandler, params RoutedEvent[] routedEvents)
: this(adornedElement, routedEventHandler, null, routedEvents) { }
protected override int VisualChildrenCount => _visualChildren.Count;
protected override Visual GetVisualChild(int index) => _visualChildren[index];
protected override Size MeasureOverride(Size constraint)
{
_border.Measure(constraint);
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size finalSize)
{
_border.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
return finalSize;
}
public void ShowWithAnimation(double seconds = 1)
{
var doubleAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
BeginAnimation(OpacityProperty, doubleAnimation);
var marginAnimation = new ThicknessAnimation
{
From = this.GetMargin4Animation(),
To = new Thickness(0),
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
_border.BeginAnimation(MarginProperty, marginAnimation);
}
public void HideWithAnimation(double seconds = 1)
{
var doubleAnimation = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
doubleAnimation.Completed += (s, e) =>
{
if (this._routedEventHandler != null)
foreach (var routedEvent in this._routedEvents)
this.RemoveHandler(routedEvent, this._routedEventHandler);
var layer = AdornerLayer.GetAdornerLayer(AdornedElement);
layer?.Remove(this);
};
BeginAnimation(OpacityProperty, doubleAnimation);
var marginAnimation = new ThicknessAnimation
{
From = new Thickness(0),
To = this.GetMargin4Animation(),
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
_border.BeginAnimation(MarginProperty, marginAnimation);
}
}
}