196 lines
7.0 KiB
C#
196 lines
7.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Data;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Media;
|
|
using HandyControl.Data;
|
|
|
|
namespace HandyControl.Interactivity
|
|
{
|
|
public abstract class FluidMoveBehaviorBase : Behavior<FrameworkElement>
|
|
{
|
|
public static readonly DependencyProperty AppliesToProperty = DependencyProperty.Register("AppliesTo",
|
|
typeof(FluidMoveScope), typeof(FluidMoveBehaviorBase), new PropertyMetadata(FluidMoveScope.Self));
|
|
|
|
protected static readonly DependencyProperty IdentityTagProperty =
|
|
DependencyProperty.RegisterAttached("IdentityTag", typeof(object), typeof(FluidMoveBehaviorBase),
|
|
new PropertyMetadata(null));
|
|
|
|
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive",
|
|
typeof(bool), typeof(FluidMoveBehaviorBase), new PropertyMetadata(ValueBoxes.TrueBox));
|
|
|
|
private static DateTime LastPurgeTick = DateTime.MinValue;
|
|
|
|
private static readonly TimeSpan MinTickDelta = TimeSpan.FromSeconds(0.5);
|
|
|
|
private static DateTime NextToLastPurgeTick = DateTime.MinValue;
|
|
|
|
internal static Dictionary<object, TagData> TagDictionary = new Dictionary<object, TagData>();
|
|
|
|
public static readonly DependencyProperty TagPathProperty = DependencyProperty.Register("TagPath",
|
|
typeof(string), typeof(FluidMoveBehaviorBase), new PropertyMetadata(string.Empty));
|
|
|
|
public static readonly DependencyProperty TagProperty = DependencyProperty.Register("Tag",
|
|
typeof(TagType), typeof(FluidMoveBehaviorBase), new PropertyMetadata(TagType.Element));
|
|
|
|
public FluidMoveScope AppliesTo
|
|
{
|
|
get =>
|
|
(FluidMoveScope) GetValue(AppliesToProperty);
|
|
set => SetValue(AppliesToProperty, value);
|
|
}
|
|
|
|
public bool IsActive
|
|
{
|
|
get =>
|
|
(bool) GetValue(IsActiveProperty);
|
|
set => SetValue(IsActiveProperty, value);
|
|
}
|
|
|
|
protected virtual bool ShouldSkipInitialLayout =>
|
|
Tag == TagType.DataContext;
|
|
|
|
public TagType Tag
|
|
{
|
|
get =>
|
|
(TagType) GetValue(TagProperty);
|
|
set => SetValue(TagProperty, value);
|
|
}
|
|
|
|
public string TagPath
|
|
{
|
|
get =>
|
|
(string) GetValue(TagPathProperty);
|
|
set => SetValue(TagPathProperty, value);
|
|
}
|
|
|
|
private void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
|
|
{
|
|
if (IsActive)
|
|
{
|
|
if (DateTime.Now - LastPurgeTick >= MinTickDelta)
|
|
{
|
|
List<object> list = null;
|
|
foreach (var pair in TagDictionary)
|
|
if (pair.Value.Timestamp < NextToLastPurgeTick)
|
|
{
|
|
if (list == null) list = new List<object>();
|
|
list.Add(pair.Key);
|
|
}
|
|
|
|
if (list != null)
|
|
foreach (var obj2 in list)
|
|
TagDictionary.Remove(obj2);
|
|
NextToLastPurgeTick = LastPurgeTick;
|
|
LastPurgeTick = DateTime.Now;
|
|
}
|
|
|
|
if (AppliesTo == FluidMoveScope.Self)
|
|
{
|
|
UpdateLayoutTransition(AssociatedObject);
|
|
}
|
|
else
|
|
{
|
|
if (AssociatedObject is Panel associatedObject)
|
|
foreach (FrameworkElement element in associatedObject.Children)
|
|
UpdateLayoutTransition(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void EnsureTags(FrameworkElement child)
|
|
{
|
|
if (Tag == TagType.DataContext &&
|
|
!(child.ReadLocalValue(IdentityTagProperty) is BindingExpression))
|
|
child.SetBinding(IdentityTagProperty, new Binding(TagPath));
|
|
}
|
|
|
|
protected static object GetIdentityTag(DependencyObject obj)
|
|
{
|
|
return obj.GetValue(IdentityTagProperty);
|
|
}
|
|
|
|
private static FrameworkElement GetVisualRoot(FrameworkElement child)
|
|
{
|
|
while (true)
|
|
{
|
|
if (!(VisualTreeHelper.GetParent(child) is FrameworkElement parent)) return child;
|
|
if (AdornerLayer.GetAdornerLayer(parent) == null) return child;
|
|
child = parent;
|
|
}
|
|
}
|
|
|
|
protected override void OnAttached()
|
|
{
|
|
base.OnAttached();
|
|
AssociatedObject.LayoutUpdated += AssociatedObject_LayoutUpdated;
|
|
}
|
|
|
|
protected override void OnDetaching()
|
|
{
|
|
base.OnDetaching();
|
|
AssociatedObject.LayoutUpdated -= AssociatedObject_LayoutUpdated;
|
|
}
|
|
|
|
protected static void SetIdentityTag(DependencyObject obj, object value)
|
|
{
|
|
obj.SetValue(IdentityTagProperty, value);
|
|
}
|
|
|
|
internal static Rect TranslateRect(Rect rect, FrameworkElement from, FrameworkElement to)
|
|
{
|
|
if (from == null || to == null) return rect;
|
|
var point = new Point(rect.Left, rect.Top);
|
|
point = from.TransformToVisual(to).Transform(point);
|
|
return new Rect(point.X, point.Y, rect.Width, rect.Height);
|
|
}
|
|
|
|
private void UpdateLayoutTransition(FrameworkElement child)
|
|
{
|
|
if (child.Visibility != Visibility.Collapsed && child.IsLoaded || !ShouldSkipInitialLayout)
|
|
{
|
|
var visualRoot = GetVisualRoot(child);
|
|
var newTagData = new TagData
|
|
{
|
|
Parent = VisualTreeHelper.GetParent(child) as FrameworkElement,
|
|
ParentRect = ExtendedVisualStateManager.GetLayoutRect(child),
|
|
Child = child,
|
|
Timestamp = DateTime.Now
|
|
};
|
|
try
|
|
{
|
|
newTagData.AppRect = TranslateRect(newTagData.ParentRect, newTagData.Parent, visualRoot);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
if (ShouldSkipInitialLayout) return;
|
|
}
|
|
|
|
EnsureTags(child);
|
|
var identityTag = GetIdentityTag(child) ?? child;
|
|
UpdateLayoutTransitionCore(child, visualRoot, identityTag, newTagData);
|
|
}
|
|
}
|
|
|
|
internal abstract void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root,
|
|
object tag, TagData newTagData);
|
|
|
|
|
|
internal class TagData
|
|
{
|
|
public Rect AppRect { get; set; }
|
|
|
|
public FrameworkElement Child { get; set; }
|
|
|
|
public object InitialTag { get; set; }
|
|
|
|
public FrameworkElement Parent { get; set; }
|
|
|
|
public Rect ParentRect { get; set; }
|
|
|
|
public DateTime Timestamp { get; set; }
|
|
}
|
|
}
|
|
} |