StyledElementBehavior.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using System;
  2. using System.Diagnostics;
  3. using System.Globalization;
  4. using Avalonia.Controls;
  5. using Avalonia.Reactive;
  6. namespace Avalonia.Xaml.Interactivity;
  7. /// <summary>
  8. /// A base class for behaviors, implementing the basic plumbing of <see cref="IBehavior"/>.
  9. /// </summary>
  10. public abstract class StyledElementBehavior : StyledElement, IBehavior, IBehaviorEventsHandler
  11. {
  12. private IDisposable? _dataContextDisposable;
  13. /// <summary>
  14. /// Identifies the <seealso cref="IsEnabled"/> avalonia property.
  15. /// </summary>
  16. public static readonly StyledProperty<bool> IsEnabledProperty =
  17. AvaloniaProperty.Register<StyledElementBehavior, bool>(nameof(IsEnabled), defaultValue: true);
  18. /// <summary>
  19. /// Gets the <see cref="AvaloniaObject"/> to which the behavior is attached.
  20. /// </summary>
  21. public AvaloniaObject? AssociatedObject { get; private set; }
  22. /// <summary>
  23. /// Gets the <see cref="StyledElement"/> to which this behavior is attached.
  24. /// </summary>
  25. public StyledElement? AssociatedStyledElement => AssociatedObject as StyledElement;
  26. /// <summary>
  27. /// Gets or sets a value indicating whether this instance is enabled.
  28. /// </summary>
  29. /// <value><c>true</c> if this instance is enabled; otherwise, <c>false</c>.</value>
  30. public bool IsEnabled
  31. {
  32. get => GetValue(IsEnabledProperty);
  33. set => SetValue(IsEnabledProperty, value);
  34. }
  35. /// <summary>
  36. /// Attaches the behavior to the specified <see cref="AvaloniaObject"/>.
  37. /// </summary>
  38. /// <param name="associatedObject">The <see cref="AvaloniaObject"/> to which to attach.</param>
  39. /// <exception cref="ArgumentNullException"><paramref name="associatedObject"/> is null.</exception>
  40. public void Attach(AvaloniaObject? associatedObject)
  41. {
  42. if (Equals(associatedObject, AssociatedObject))
  43. {
  44. return;
  45. }
  46. if (AssociatedObject is not null)
  47. {
  48. throw new InvalidOperationException(string.Format(
  49. CultureInfo.CurrentCulture,
  50. "An instance of a behavior cannot be attached to more than one object at a time."));
  51. }
  52. Debug.Assert(associatedObject is not null, "Cannot attach the behavior to a null object.");
  53. AssociatedObject = associatedObject ?? throw new ArgumentNullException(nameof(associatedObject));
  54. _dataContextDisposable = SynchronizeDataContext(associatedObject);
  55. OnAttached();
  56. }
  57. /// <summary>
  58. /// Detaches the behaviors from the <see cref="AssociatedObject"/>.
  59. /// </summary>
  60. public void Detach()
  61. {
  62. OnDetaching();
  63. _dataContextDisposable?.Dispose();
  64. AssociatedObject = null;
  65. }
  66. /// <summary>
  67. /// Called after the behavior is attached to the <see cref="AssociatedObject"/>.
  68. /// </summary>
  69. /// <remarks>
  70. /// Override this to hook up functionality to the <see cref="AssociatedObject"/>
  71. /// </remarks>
  72. protected virtual void OnAttached()
  73. {
  74. }
  75. /// <summary>
  76. /// Called when the behavior is being detached from its <see cref="AssociatedObject"/>.
  77. /// </summary>
  78. /// <remarks>
  79. /// Override this to unhook functionality from the <see cref="AssociatedObject"/>
  80. /// </remarks>
  81. protected virtual void OnDetaching()
  82. {
  83. }
  84. void IBehaviorEventsHandler.AttachedToVisualTreeEventHandler()
  85. {
  86. AttachBehaviorToLogicalTree();
  87. OnAttachedToVisualTree();
  88. }
  89. void IBehaviorEventsHandler.DetachedFromVisualTreeEventHandler()
  90. {
  91. DetachBehaviorFromLogicalTree();
  92. OnDetachedFromVisualTree();
  93. }
  94. void IBehaviorEventsHandler.AttachedToLogicalTreeEventHandler()
  95. {
  96. AttachBehaviorToLogicalTree();
  97. OnAttachedToLogicalTree();
  98. }
  99. void IBehaviorEventsHandler.DetachedFromLogicalTreeEventHandler()
  100. {
  101. DetachBehaviorFromLogicalTree();
  102. OnDetachedFromLogicalTree();
  103. }
  104. void IBehaviorEventsHandler.LoadedEventHandler() => OnLoaded();
  105. void IBehaviorEventsHandler.UnloadedEventHandler() => OnUnloaded();
  106. /// <summary>
  107. /// Called after the <see cref="AssociatedObject"/> is attached to the visual tree.
  108. /// </summary>
  109. /// <remarks>
  110. /// Invoked only when the <see cref="AssociatedObject"/> is of type <see cref="Control"/>.
  111. /// </remarks>
  112. protected virtual void OnAttachedToVisualTree()
  113. {
  114. }
  115. /// <summary>
  116. /// Called when the <see cref="AssociatedObject"/> is being detached from the visual tree.
  117. /// </summary>
  118. /// <remarks>
  119. /// Invoked only when the <see cref="AssociatedObject"/> is of type <see cref="Control"/>.
  120. /// </remarks>
  121. protected virtual void OnDetachedFromVisualTree()
  122. {
  123. }
  124. /// <summary>
  125. /// Called after the <see cref="AssociatedObject"/> is attached to the logical tree.
  126. /// </summary>
  127. /// <remarks>
  128. /// Invoked only when the <see cref="AssociatedObject"/> is of type <see cref="Control"/>.
  129. /// </remarks>
  130. protected virtual void OnAttachedToLogicalTree()
  131. {
  132. }
  133. /// <summary>
  134. /// Called when the <see cref="AssociatedObject"/> is being detached from the logical tree.
  135. /// </summary>
  136. /// <remarks>
  137. /// Invoked only when the <see cref="AssociatedObject"/> is of type <see cref="Control"/>.
  138. /// </remarks>
  139. protected virtual void OnDetachedFromLogicalTree()
  140. {
  141. }
  142. /// <summary>
  143. /// Called after the <see cref="AssociatedObject"/> is loaded.
  144. /// </summary>
  145. /// <remarks>
  146. /// Invoked only when the <see cref="AssociatedObject"/> is of type <see cref="Control"/>.
  147. /// </remarks>
  148. protected virtual void OnLoaded()
  149. {
  150. }
  151. /// <summary>
  152. /// Called when the <see cref="AssociatedObject"/> is unloaded.
  153. /// </summary>
  154. /// <remarks>
  155. /// Invoked only when the <see cref="AssociatedObject"/> is of type <see cref="Control"/>.
  156. /// </remarks>
  157. protected virtual void OnUnloaded()
  158. {
  159. }
  160. private void AttachBehaviorToLogicalTree()
  161. {
  162. if (AssociatedObject is not StyledElement styledElement)
  163. {
  164. return;
  165. }
  166. if (styledElement.Parent is not null)
  167. {
  168. ((ISetLogicalParent)this).SetParent(null);
  169. ((ISetLogicalParent)this).SetParent(styledElement);
  170. }
  171. }
  172. private void DetachBehaviorFromLogicalTree()
  173. {
  174. ((ISetLogicalParent)this).SetParent(null);
  175. }
  176. private IDisposable? SynchronizeDataContext(AvaloniaObject associatedObject)
  177. {
  178. if (associatedObject is StyledElement styledElement)
  179. {
  180. return styledElement
  181. .GetObservable(DataContextProperty)
  182. .Subscribe(new AnonymousObserver<object?>(x =>
  183. {
  184. SetCurrentValue(DataContextProperty, x);
  185. }));
  186. }
  187. return default;
  188. }
  189. }