291 lines
12 KiB
C#
291 lines
12 KiB
C#
|
|
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<bool> 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<bool>? 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<bool>? 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<bool>? 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<string, UIElement> LayerOwners = new Dictionary<string, UIElement>();
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|