367 lines
16 KiB
C#
367 lines
16 KiB
C#
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|