215 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
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);
 | 
						||
        }
 | 
						||
    }
 | 
						||
}
 |