Files
Future/Future.Contract/Adorners/FutureDrawer.cs
2025-08-30 17:19:57 +08:00

367 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Future.Contract
{
/// <summary>
/// [Content]属性就是抽屉中要呈现的视图
/// <MyNamespace:FutureDrawer/>
/// </summary>
[TemplatePart(Name = "TitleBorder", Type = typeof(Border))]//执行透明度动画
[TemplatePart(Name = "ContentView", Type = typeof(Border))]//执行Margin动画
[TemplatePart(Name = "CloseHandle", Type = typeof(Button))]//执行透明度动化
[TemplatePart(Name = "DrawerRoot", Type = typeof(DockPanel))]//处理窗口最大化时的边距
public class FutureDrawer : ContentControl
{
static FutureDrawer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FutureDrawer), new FrameworkPropertyMetadata(typeof(FutureDrawer)));
}
#region
private const string TitleBorder = "TitleBorder";
private const string ContentView = "ContentView";
private const string CloseButton = "CloseHandle";
private const string DrawerRoot = "DrawerRoot";
private Border mContentView;
private Button mCloseButton;
private DockPanel mDrawerRoot;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
mContentView = GetTemplateChild(ContentView) as Border;
mCloseButton = GetTemplateChild(CloseButton) as Button;
mDrawerRoot = GetTemplateChild(DrawerRoot) as DockPanel;
if (mCloseButton != null) mCloseButton.Click += (s, e) =>
{
if(this.ApplyCloseDrawer != null && !this.ApplyCloseDrawer(this.Sign)) return;
else IsOpen = false;
};
this.Loaded += (s, e) => IsOpen = true;
}
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(FutureDrawer), new PropertyMetadata(false, (s, e) =>
{
var drawer = s as FutureDrawer;
if (e.NewValue is bool b && b)
{
drawer?.StartAnimationIn();
}
else
{
drawer?.StartAnimationOut();
}
}));
private async void StartAnimationIn(float seconds = 0.3f)
{
var sb = new Storyboard();
var offset = mDrawerRoot?.ActualWidth ?? 0;
var animation = new ThicknessAnimation
{
Duration = new Duration(TimeSpan.FromSeconds(seconds)),
From = new Thickness(-offset, 0, offset, 0),
To = new Thickness(0)
};
Storyboard.SetTargetProperty(animation, new PropertyPath("Margin"));
sb.Children.Add(animation);
sb.Begin(mDrawerRoot);
await Task.Delay((int)(seconds * 1000));
}
private async void StartAnimationOut(float seconds = 0.3f)
{
var sb = new Storyboard();
var offset = mDrawerRoot?.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(mDrawerRoot);
await Task.Delay((int)(seconds * 1000));
this.DrawerClosed?.Invoke();
}
#endregion
public Dock Dock
{
get { return (Dock)GetValue(DockProperty); }
set { SetValue(DockProperty, value); }
}
// Using a DependencyProperty as the backing store for Dock. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DockProperty =
DependencyProperty.Register("Dock", typeof(Dock), typeof(FutureDrawer), new PropertyMetadata(Dock.Right));
public double TitleHeight
{
get { return (double)GetValue(TitleHeightProperty); }
set { SetValue(TitleHeightProperty, value); }
}
// Using a DependencyProperty as the backing store for TitleHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleHeightProperty =
DependencyProperty.Register("TitleHeight", typeof(double), typeof(FutureDrawer), new PropertyMetadata(40.0));
public double TitleIndent
{
get { return (double)GetValue(TitleIndentProperty); }
set { SetValue(TitleIndentProperty, value); }
}
// Using a DependencyProperty as the backing store for TitleIndent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleIndentProperty =
DependencyProperty.Register("TitleIndent", typeof(double), typeof(FutureDrawer), new PropertyMetadata(0.0));
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(FutureDrawer), new PropertyMetadata(""));
public int? Sign
{
get { return (int?)GetValue(SignProperty); }
set { SetValue(SignProperty, value); }
}
// Using a DependencyProperty as the backing store for Sign. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SignProperty =
DependencyProperty.Register("Sign", typeof(int?), typeof(FutureDrawer), new PropertyMetadata(0));
/// <summary>
/// 当抽屉准备关闭时发出的申请
/// </summary>
public event Func<int?, bool> ApplyCloseDrawer;
/// <summary>
/// 抽屉关闭后发出通知的委托
/// </summary>
public Action DrawerClosed;
/// <summary>
/// 默认构造:用来使用设计器构造抽屉模板
/// </summary>
public FutureDrawer() { }
public void Close()
{
this.StartAnimationOut();
}
/// <summary>
/// 内部构造:用来复刻当前抽屉模板创建新的抽屉来显示内容
/// </summary>
/// <param name="view">要呈现的视图</param>
/// <param name="title">抽屉的标题</param>
/// <param name="callBackSign">用于CallBack的识别码</param>
/// <param name="applyCloseCallBack">抽屉要关闭时的回调通知</param>
private FutureDrawer(UIElement view, string title, int? callBackSign = null, Func<int?, bool> applyCloseCallBack = null)
{
this.Content = view;
this.Title = title;
this.Sign = Sign;
this.ApplyCloseDrawer = applyCloseCallBack;
}
/// <summary>
/// 克隆当前抽屉的所有属性
/// </summary>
/// <returns></returns>
private FutureDrawer Clone(UIElement view, string title, int? callBackSign = null, Func<int?, bool> applyCloseCallBack = null)
{
FutureDrawer result = new FutureDrawer(view, title, callBackSign, applyCloseCallBack);
result.Dock = this.Dock;
result.TitleHeight = this.TitleHeight;
result.TitleIndent = this.TitleIndent;
result.Background = this.Background;
result.Foreground = this.Foreground;
result.Margin = this.Margin;
result.HorizontalAlignment = this.HorizontalAlignment;
result.VerticalAlignment = this.VerticalAlignment;
result.HorizontalContentAlignment = this.HorizontalContentAlignment;
result.VerticalContentAlignment = this.VerticalContentAlignment;
result.FontSize = this.FontSize;
return result;
}
private Window GetWindow(FrameworkElement origin)
{
if (origin == null) return null;
while (origin != null && origin is not Window) origin = origin.Parent as FrameworkElement;
return origin as Window;
}
/// <summary>
/// 抽屉使用的装饰器,如果存在则证明抽屉曾经打开过,不需要重复创建
/// </summary>
private DrawerAdorner _adorner;
/// <summary>
/// 将当前抽屉显示在目标UIElement【target】中
/// </summary>
/// <param name="target">要显示当前抽屉的UIElement元素</param>
public void ShowInTarget(FrameworkElement target, object content = null)
{
this.Content = content ?? this.Content;
Window window = GetWindow(target);
if (window != null)
{
if (window.WindowState == WindowState.Maximized) this.Padding = new Thickness(8);
window.StateChanged += (s, e) =>
{
Window w = s as Window;
if (w != null)
{
if (w.WindowState == WindowState.Maximized)
this.Padding = new Thickness(8);
else this.Padding = new Thickness(0);
}
};
}
//TODO:这里需要处理多个抽屉叠加的效果
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(target);
_adorner = _adorner ?? new DrawerAdorner(adornerLayer) { Child = this };
this.DrawerClosed = () => adornerLayer.Remove(_adorner);
adornerLayer.Add(_adorner);
}
/// <summary>
/// 在参照点【origin】所在的视觉树向上查找Window对象如果找到则在其可视范围内显示抽屉
/// </summary>
/// <param name="origin">参照点</param>
public void ShowInWindow(FrameworkElement origin, object content = null)
{
FrameworkElement target = origin;
while (target != null && !(target is Window)) target = target.Parent as FrameworkElement;
if (target is Window) this.ShowInTarget(target, content);
}
/// <summary>
/// 在参照点【origin】所在的视觉树内名称为【targetName】名称的UIElement可视范围内显示抽屉
/// </summary>
/// <param name="origin">参照点</param>
/// <param name="targetName">目标UIElement的名称</param>
public void ShowInNamedView(FrameworkElement origin, string targetName, object content = null)
{
FrameworkElement target = origin;
while (target != null && target.Parent != null && target.Name != targetName) target = target.Parent as FrameworkElement;
if (target != null && target.Name != targetName) target = target.FindName(targetName) as FrameworkElement;
if (target is UIElement && target.Name == targetName) this.ShowInTarget(target, content);
}
/// <summary>
/// 以当前抽屉为模板复制一个新的抽屉在目标UIElement的可视范围内显示抽屉
/// </summary>
/// <param name="view">抽屉中承载的内容</param>
/// <param name="target">承载抽屉的UI元素</param>
/// <param name="title">抽屉的标题</param>
/// <param name="callBackSign">请求关闭回调时传入的识别码</param>
/// <param name="applyCloseCallBack">抽屉请求关闭时的回调</param>
public FutureDrawer ShowNewInTarget(UIElement view, FrameworkElement target, string title, int? callBackSign = null, Func<int?, bool> applyCloseCallBack = null)
{
FutureDrawer drawer = this.Clone(view, title, callBackSign, applyCloseCallBack);
drawer.ShowInTarget(target);
return drawer;
}
/// <summary>
/// 以当前抽屉为模板复制一个新的抽屉以参照原点【origin】为起点沿着视觉树向上查找到的第一个Window对象在其可视范围内显示抽屉
/// </summary>
/// <param name="view">抽屉中承载的内容</param>
/// <param name="origin">查找的参照点</param>
/// <param name="title">抽屉的标题</param>
/// <param name="callBackSign">请求关闭回调时传入的识别码</param>
/// <param name="applyCloseCallBack">抽屉请求关闭时的回调</param>
public void ShowNewInWindow(UIElement view, FrameworkElement origin, string title, int? callBackSign = null, Func<int?, bool> applyCloseCallBack = null)
{
FutureDrawer drawer = this.Clone(view, title, callBackSign, applyCloseCallBack);
drawer?.ShowInWindow(origin);
}
/// <summary>
/// 以当前抽屉为模板复制一个新的抽屉在参照原点【origin】的视觉树内查找【target】名称的UIElement元素在其可视范围内显示抽屉
/// </summary>
/// <param name="view">抽屉中承载的内容</param>
/// <param name="origin">查找的参照点</param>
/// <param name="targetName">要查找的目标UIElement的名称</param>
/// <param name="title">抽屉的标题</param>
/// <param name="callBackSign">请求关闭回调时传入的识别码</param>
/// <param name="applyCloseCallBack">抽屉请求关闭时的回调</param>
public void ShowNewInNamedView(UIElement view, FrameworkElement origin, string targetName, string title, int? callBackSign = null, Func<int?, bool> applyCloseCallBack = null)
{
FutureDrawer drawer = this.Clone(view, title, callBackSign, applyCloseCallBack);
drawer?.ShowInNamedView(origin, targetName);
}
#region
/// <summary>
/// 专为抽屉使用的装饰器类
/// </summary>
private class DrawerAdorner : Adorner
{
/// <summary>
/// Adorner本身没有Child属性所以这里需要自己加一个
/// </summary>
private UIElement mChild;
public DrawerAdorner(UIElement adornedElement) : base(adornedElement) { }
public UIElement Child
{
get => mChild;
set
{
if (value == null) RemoveVisualChild(mChild);
else AddVisualChild(value);
mChild = value;
}
}
//重写VisualChildrenCount 表示此控件只有一个子控件
protected override int VisualChildrenCount => 1;
//控件计算大小的时候,我们在装饰层上添加的控件也计算一下大小
protected override Size ArrangeOverride(Size finalSize)
{
mChild?.Arrange(new Rect(finalSize));
return finalSize;
}
//重写GetVisualChild,返回我们添加的控件
protected override Visual GetVisualChild(int index)
{
if (index == 0 && mChild != null) return mChild;
return base.GetVisualChild(index);
}
}
#endregion
}
}