FluidMoveBehaviorBase.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Data;
  6. using System.Windows.Documents;
  7. using System.Windows.Media;
  8. using HandyControl.Data;
  9. namespace HandyControl.Interactivity;
  10. public abstract class FluidMoveBehaviorBase : Behavior<FrameworkElement>
  11. {
  12. public static readonly DependencyProperty AppliesToProperty = DependencyProperty.Register("AppliesTo",
  13. typeof(FluidMoveScope), typeof(FluidMoveBehaviorBase), new PropertyMetadata(FluidMoveScope.Self));
  14. protected static readonly DependencyProperty IdentityTagProperty =
  15. DependencyProperty.RegisterAttached("IdentityTag", typeof(object), typeof(FluidMoveBehaviorBase),
  16. new PropertyMetadata(null));
  17. public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive",
  18. typeof(bool), typeof(FluidMoveBehaviorBase), new PropertyMetadata(ValueBoxes.TrueBox));
  19. private static DateTime LastPurgeTick = DateTime.MinValue;
  20. private static readonly TimeSpan MinTickDelta = TimeSpan.FromSeconds(0.5);
  21. private static DateTime NextToLastPurgeTick = DateTime.MinValue;
  22. internal static Dictionary<object, TagData> TagDictionary = new();
  23. public static readonly DependencyProperty TagPathProperty = DependencyProperty.Register("TagPath",
  24. typeof(string), typeof(FluidMoveBehaviorBase), new PropertyMetadata(string.Empty));
  25. public static readonly DependencyProperty TagProperty = DependencyProperty.Register("Tag",
  26. typeof(TagType), typeof(FluidMoveBehaviorBase), new PropertyMetadata(TagType.Element));
  27. public FluidMoveScope AppliesTo
  28. {
  29. get =>
  30. (FluidMoveScope) GetValue(AppliesToProperty);
  31. set => SetValue(AppliesToProperty, value);
  32. }
  33. public bool IsActive
  34. {
  35. get =>
  36. (bool) GetValue(IsActiveProperty);
  37. set => SetValue(IsActiveProperty, ValueBoxes.BooleanBox(value));
  38. }
  39. protected virtual bool ShouldSkipInitialLayout =>
  40. Tag == TagType.DataContext;
  41. public TagType Tag
  42. {
  43. get =>
  44. (TagType) GetValue(TagProperty);
  45. set => SetValue(TagProperty, value);
  46. }
  47. public string TagPath
  48. {
  49. get =>
  50. (string) GetValue(TagPathProperty);
  51. set => SetValue(TagPathProperty, value);
  52. }
  53. private void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
  54. {
  55. if (IsActive)
  56. {
  57. if (DateTime.Now - LastPurgeTick >= MinTickDelta)
  58. {
  59. List<object> list = null;
  60. foreach (var pair in TagDictionary)
  61. if (pair.Value.Timestamp < NextToLastPurgeTick)
  62. {
  63. if (list == null) list = new List<object>();
  64. list.Add(pair.Key);
  65. }
  66. if (list != null)
  67. foreach (var obj2 in list)
  68. TagDictionary.Remove(obj2);
  69. NextToLastPurgeTick = LastPurgeTick;
  70. LastPurgeTick = DateTime.Now;
  71. }
  72. if (AppliesTo == FluidMoveScope.Self)
  73. {
  74. UpdateLayoutTransition(AssociatedObject);
  75. }
  76. else
  77. {
  78. if (AssociatedObject is Panel associatedObject)
  79. foreach (FrameworkElement element in associatedObject.Children)
  80. UpdateLayoutTransition(element);
  81. }
  82. }
  83. }
  84. protected virtual void EnsureTags(FrameworkElement child)
  85. {
  86. if (Tag == TagType.DataContext &&
  87. !(child.ReadLocalValue(IdentityTagProperty) is BindingExpression))
  88. child.SetBinding(IdentityTagProperty, new Binding(TagPath));
  89. }
  90. protected static object GetIdentityTag(DependencyObject obj)
  91. {
  92. return obj.GetValue(IdentityTagProperty);
  93. }
  94. private static FrameworkElement GetVisualRoot(FrameworkElement child)
  95. {
  96. while (true)
  97. {
  98. if (!(VisualTreeHelper.GetParent(child) is FrameworkElement parent)) return child;
  99. if (AdornerLayer.GetAdornerLayer(parent) == null) return child;
  100. child = parent;
  101. }
  102. }
  103. protected override void OnAttached()
  104. {
  105. base.OnAttached();
  106. AssociatedObject.LayoutUpdated += AssociatedObject_LayoutUpdated;
  107. }
  108. protected override void OnDetaching()
  109. {
  110. base.OnDetaching();
  111. AssociatedObject.LayoutUpdated -= AssociatedObject_LayoutUpdated;
  112. }
  113. protected static void SetIdentityTag(DependencyObject obj, object value)
  114. {
  115. obj.SetValue(IdentityTagProperty, value);
  116. }
  117. internal static Rect TranslateRect(Rect rect, FrameworkElement from, FrameworkElement to)
  118. {
  119. if (from == null || to == null) return rect;
  120. var point = new Point(rect.Left, rect.Top);
  121. point = from.TransformToVisual(to).Transform(point);
  122. return new Rect(point.X, point.Y, rect.Width, rect.Height);
  123. }
  124. private void UpdateLayoutTransition(FrameworkElement child)
  125. {
  126. if (child.Visibility != Visibility.Collapsed && child.IsLoaded || !ShouldSkipInitialLayout)
  127. {
  128. var visualRoot = GetVisualRoot(child);
  129. var newTagData = new TagData
  130. {
  131. Parent = VisualTreeHelper.GetParent(child) as FrameworkElement,
  132. ParentRect = ExtendedVisualStateManager.GetLayoutRect(child),
  133. Child = child,
  134. Timestamp = DateTime.Now
  135. };
  136. try
  137. {
  138. newTagData.AppRect = TranslateRect(newTagData.ParentRect, newTagData.Parent, visualRoot);
  139. }
  140. catch (ArgumentException)
  141. {
  142. if (ShouldSkipInitialLayout) return;
  143. }
  144. EnsureTags(child);
  145. var identityTag = GetIdentityTag(child) ?? child;
  146. UpdateLayoutTransitionCore(child, visualRoot, identityTag, newTagData);
  147. }
  148. }
  149. internal abstract void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root,
  150. object tag, TagData newTagData);
  151. internal class TagData
  152. {
  153. public Rect AppRect { get; set; }
  154. public FrameworkElement Child { get; set; }
  155. public object InitialTag { get; set; }
  156. public FrameworkElement Parent { get; set; }
  157. public Rect ParentRect { get; set; }
  158. public DateTime Timestamp { get; set; }
  159. }
  160. }