Files
Future/Future.Contract/Adorners/DrawerAdorner.cs

291 lines
12 KiB
C#
Raw Permalink Normal View History

2025-08-30 17:19:57 +08:00
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
}
}