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 { /// /// 抽屉窗口装饰器 /// 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)); /// /// 动画方向:如果为「null」则使用缩放动画 /// private readonly Dock? _direction; /// /// 根据抽屉加载方向创建边距动画所需要的「Thickness」对象 /// /// 动画需要的「Thickness」对象 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); } } /// /// 注册附属视图;注册前请确保布局属性被正确设置 /// /// 要注册的附属视图;注册前请确保布局属性被正确设置 /// 在注册的方向 /// 当前装饰器对象,方便连续操作;不会创建新的装饰器 public DrawerViewer RegisterAttachedView(UIElement attachedView, Dock? dock) { dock ??= (Dock)(((int)(this._direction ?? Dock.Left) + 2) % 4); return this; } /// /// 在特定「UIElement」上创建「WindowDrawer」蒙层 /// /// 目标「UIElement」元素 /// 抽屉的加载方向,如果不设置则使用缩放动画 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; /// /// 在特定「UIElement」上创建「WindowDrawer」蒙层,同时绑定特定路由事件 /// /// 目标「UIElement」元素 /// 路由事件处理程序 /// 抽屉的加载方向,如果不设置则使用缩放动画 /// 要绑定的路由事件 /// /// 如果不设置「routedEvents」参数则注册「ButtonBase.ClickEvent」事件 /// 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); } } /// /// 在特定「UIElement」上创建「WindowDrawer」蒙层,同时绑定特定路由事件 /// /// 目标「UIElement」元素 /// 路由事件处理程序 /// 要绑定的路由事件 /// /// 如果不设置「routedEvents」参数则注册「ButtonBase.ClickEvent」事件 /// 抽屉采用缩放方式加载 /// 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); } } }