using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Animation; namespace Future.Contract { public sealed class DrawerAdorner : Adorner { private VisualCollection mVisuals; private Border mRoot; private AdornerLayer? mLayer; private Size mSize; private bool mIsShowed = false; private bool mIsClosed = false; private Func mApplyCloseCallback; public DrawerAdorner(UIElement adornedElement) : base(adornedElement) { mVisuals = new VisualCollection(this); mRoot = new Border(); mVisuals.Add(mRoot); this.mLayer = AdornerLayer.GetAdornerLayer(adornedElement); //注意:开场动画必须在此处执行 this.Loaded += (ss, ee) => this.StartAnimationIn(); //this.Unloaded += (ss, ee) => this.StartAnimationOut(); } protected override int VisualChildrenCount => mVisuals.Count; protected override Visual GetVisualChild(int index) => mVisuals[index]; protected override Size ArrangeOverride(Size finalSize) { mSize = finalSize; mRoot.Arrange(new Rect(new Point(), finalSize)); return finalSize; } #pragma warning disable CS0628 // 在密封类型中声明了新的保护成员 protected void SetHandleVisibility(Visibility visibility) #pragma warning restore CS0628 // 在密封类型中声明了新的保护成员 { if (this.mRoot.Child != null && this.mRoot.Child is DrawerTemplate) { DrawerTemplate template = this.mRoot.Child as DrawerTemplate; template.HandleVisibility = visibility; } } public void ShowWithTemplate(DrawerTemplate? template, FrameworkElement view, string title, Func? applyCloseCallback = null) { if (template == null && view == null) return; view?.ClearValue(FrameworkElement.WidthProperty); view?.ClearValue(FrameworkElement.HeightProperty); this.mApplyCloseCallback = applyCloseCallback; if (template != null) { this.mRoot.Child = template; template.Title = title; template.Content = view; template.ApplyCloseDrawer += () => this.Close(); } else this.mRoot.Child = view; //隐藏同一个目标上最上层的相同装饰器的半透明区域(注意:需要防止这个逻辑执行2次) var adorners = this.mLayer?.GetAdorners(this.AdornedElement); if (adorners != null && !this.mIsShowed)//保证即使更换显示内容也不会重复处理关闭按钮的隐藏 { for (int i = adorners.Length - 1; i >= 0; i--) if (adorners[i] is DrawerAdorner) { ((DrawerAdorner)adorners[i]).SetHandleVisibility(Visibility.Collapsed); break; } } if (!this.mIsShowed) this.mLayer?.Add(this);//完成显示工作 //注意:入场动画在OnLoad事件的响应时调用 this.mIsShowed = true; this.mIsClosed = false; } public static DrawerAdorner ShowInVisual(UIElement adornedElement, DrawerTemplate? template, FrameworkElement view, string title, Func? applyCloseCallback = null) { DrawerAdorner drawer = new DrawerAdorner(adornedElement); drawer.ShowWithTemplate(template?.Clone(), view, title, applyCloseCallback); //drawer.ShowWithTemplate(template, view, title, applyCloseCallback); return drawer; } public static DrawerAdorner ShowInTarget(string targetName, DrawerTemplate? template, FrameworkElement view, string title, Func? applyCloseCallback = null) { if (LayerOwners.ContainsKey(targetName)) return ShowInVisual(LayerOwners[targetName], template, view, title, applyCloseCallback); else return null; } public void ShowOnlyView(FrameworkElement view) => this.ShowWithTemplate(null, view, null, null); public static DrawerAdorner ShowView(UIElement adornedElement, FrameworkElement view) { DrawerAdorner drawer = new DrawerAdorner(adornedElement); drawer.ShowOnlyView(view); return drawer; } public void CloseDrawer() { //强制清空请求关闭委托 this.mApplyCloseCallback = null; this.Close(); } private async void Close() { if (this.mIsClosed) return;//如果已经关闭了就不要重复执行了,会导致层叠问题 var sign = this.mApplyCloseCallback?.Invoke(); if (sign == false) return; #region 关闭动画;注意:不能单独封装为函数使用,否则会因为“异步”执行造成动画失效;如果单独封装成函数则需要将“装饰器”卸载操作一同封装 float seconds = 0.3f; var sb = new Storyboard(); //var offset = mRoot?.ActualWidth ?? 0; //var animation = new ThicknessAnimation //{ // Duration = new Duration(TimeSpan.FromSeconds(seconds)), // From = new Thickness(0), // To = new Thickness(-offset, 0, offset, 0), //}; var animation = BuildAnimation(seconds, true); Storyboard.SetTargetProperty(animation, new PropertyPath("Margin")); sb.Children.Add(animation); sb.Begin(mRoot); await Task.Delay((int)(seconds * 1000)); #endregion //开始卸载装饰器 this.SetHandleVisibility(Visibility.Collapsed); this.mRoot.Child = null; //清除模板视图的引用 this.mLayer?.Remove(this); //完成关闭装饰器工作 //显示同一个目标上最上层的相同装饰器的半透明区域 var adorners = this.mLayer?.GetAdorners(this.AdornedElement); if (adorners != null) { for (int i = adorners.Length - 1; i >= 0; i--) if (adorners[i] is DrawerAdorner) { ((DrawerAdorner)adorners[i]).SetHandleVisibility(Visibility.Visible); break; } } this.mIsClosed = true; this.mIsShowed = false; } private ThicknessAnimation BuildAnimation(float seconds = 0.3f, bool inOrOut = false) { var wOffset = mRoot?.ActualWidth ?? 0; var hOffset = mRoot?.ActualHeight ?? 0; Thickness from = new Thickness(-wOffset, 0, wOffset, 0); Thickness to = new Thickness(0); DrawerTemplate template = this.mRoot?.Child as DrawerTemplate; if (template != null) { switch (template.Dock) { case Dock.Top: from = new Thickness(0, -hOffset, 0, hOffset); break; case Dock.Bottom: from = new Thickness(0, hOffset, 0, -hOffset); break; case Dock.Right: from = new Thickness(wOffset, 0, -wOffset, 0); break; default: break; } } //如果是退出动画则需要对调From与To两个节点 if (inOrOut) { Thickness thickness = from; from = to; to = thickness; } return new ThicknessAnimation() { Duration = new Duration(TimeSpan.FromSeconds(seconds)), From = from, To = to }; } private async void StartAnimationIn(float seconds = 0.3f) { var sb = new Storyboard(); //var offset = mRoot?.ActualWidth ?? 0; //var animation = new ThicknessAnimation //{ // Duration = new Duration(TimeSpan.FromSeconds(seconds)), // From = new Thickness(-offset, 0, offset, 0), // To = new Thickness(0) //}; var animation = BuildAnimation(seconds); Storyboard.SetTargetProperty(animation, new PropertyPath("Margin")); sb.Children.Add(animation); sb.Begin(mRoot); await Task.Delay((int)(seconds * 1000)); } private async void StartAnimationOut(float seconds = 0.3f) { var sb = new Storyboard(); var offset = mRoot?.ActualWidth ?? 0; var animation = new ThicknessAnimation { Duration = new Duration(TimeSpan.FromSeconds(seconds)), From = new Thickness(0), To = new Thickness(-offset, 0, offset, 0), }; Storyboard.SetTargetProperty(animation, new PropertyPath("Margin")); sb.Children.Add(animation); sb.Begin(mRoot); await Task.Delay((int)(seconds * 1000)); this.mRoot.Child = null; this.mLayer?.Remove(this);//完成关闭工作 //显示同一个目标上最上层的相同装饰器的半透明区域 var adorners = this.mLayer?.GetAdorners(this.AdornedElement); if (adorners != null) { for (int i = adorners.Length - 1; i >= 0; i--) if (adorners[i] is DrawerAdorner) { ((DrawerAdorner)adorners[i]).SetHandleVisibility(Visibility.Visible); break; } } } #region 依赖属性 private static Dictionary LayerOwners = new Dictionary(); public static string GetLayerName(DependencyObject obj) { return (string)obj.GetValue(LayerNameProperty); } public static void SetLayerName(DependencyObject obj, string value) { obj.SetValue(LayerNameProperty, value); } // Using a DependencyProperty as the backing store for LayerName. This enables animation, styling, binding, etc... public static readonly DependencyProperty LayerNameProperty = DependencyProperty.RegisterAttached("LayerName", typeof(string), typeof(DrawerAdorner), new PropertyMetadata(null, (s, e) => { //将抽屉拥有者注册到静态区 var layerOwner = s as UIElement; if (s == null) return; if (e.OldValue != null && LayerOwners.ContainsKey((string)e.OldValue)) LayerOwners.Remove((string)e.OldValue); if (e.NewValue != null && layerOwner != null) { if (LayerOwners.ContainsKey((string)e.NewValue)) LayerOwners.Remove((string)e.NewValue); LayerOwners.Add((string)e.NewValue, layerOwner); DependencyObject d = s; while (d != null) { if (d is Window) break; var f = d as FrameworkElement; if (f != null && f is not Window) d = f.Parent; } if (d is Window) { Window w = (Window)d; w.Unloaded += (ss, ee) => LayerOwners.Remove((string)e.NewValue); } } })); #endregion } }