ContentControlMixin.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Linq;
  5. using System.Reactive.Disposables;
  6. using System.Runtime.CompilerServices;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Interactivity;
  11. using Avalonia.LogicalTree;
  12. namespace Avalonia.Controls.Mixins
  13. {
  14. /// <summary>
  15. /// Adds content control functionality to control classes.
  16. /// </summary>
  17. /// <para>
  18. /// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
  19. /// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
  20. /// keeps the control's logical children in sync with the content being displayed by the
  21. /// control.
  22. /// </para>
  23. public class ContentControlMixin
  24. {
  25. private static Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>> subscriptions =
  26. new Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>>(() =>
  27. new ConditionalWeakTable<TemplatedControl, IDisposable>());
  28. /// <summary>
  29. /// Initializes a new instance of the <see cref="SelectableMixin"/> class.
  30. /// </summary>
  31. /// <typeparam name="TControl">The control type.</typeparam>
  32. /// <param name="content">The content property.</param>
  33. /// <param name="logicalChildrenSelector">
  34. /// Given an control of <typeparamref name="TControl"/> should return the control's
  35. /// logical children collection.
  36. /// </param>
  37. /// <param name="presenterName">
  38. /// The name of the content presenter in the control's template.
  39. /// </param>
  40. public static void Attach<TControl>(
  41. AvaloniaProperty content,
  42. Func<TControl, IAvaloniaList<ILogical>> logicalChildrenSelector,
  43. string presenterName = "PART_ContentPresenter")
  44. where TControl : TemplatedControl
  45. {
  46. Contract.Requires<ArgumentNullException>(content != null);
  47. Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null);
  48. void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e)
  49. {
  50. if (s is IControl sender && sender?.TemplatedParent is TControl parent)
  51. {
  52. UpdateLogicalChild(
  53. sender,
  54. logicalChildrenSelector(parent),
  55. e.OldValue,
  56. null);
  57. }
  58. }
  59. void TemplateApplied(object s, RoutedEventArgs ev)
  60. {
  61. if (s is TControl sender)
  62. {
  63. var e = (TemplateAppliedEventArgs)ev;
  64. var presenter = e.NameScope.Find(presenterName) as IContentPresenter;
  65. if (presenter != null)
  66. {
  67. presenter.ApplyTemplate();
  68. var logicalChildren = logicalChildrenSelector(sender);
  69. var subscription = new CompositeDisposable();
  70. presenter.ChildChanging += ChildChanging;
  71. subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging));
  72. subscription.Add(presenter
  73. .GetPropertyChangedObservable(ContentPresenter.ChildProperty)
  74. .Subscribe(c => UpdateLogicalChild(
  75. sender,
  76. logicalChildren,
  77. null,
  78. c.NewValue)));
  79. UpdateLogicalChild(
  80. sender,
  81. logicalChildren,
  82. null,
  83. presenter.GetValue(ContentPresenter.ChildProperty));
  84. if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription))
  85. {
  86. subscription = new CompositeDisposable(previousSubscription, subscription);
  87. subscriptions.Value.Remove(sender);
  88. }
  89. subscriptions.Value.Add(sender, subscription);
  90. }
  91. }
  92. }
  93. TemplatedControl.TemplateAppliedEvent.AddClassHandler(
  94. typeof(TControl),
  95. TemplateApplied,
  96. RoutingStrategies.Direct);
  97. content.Changed.Subscribe(e =>
  98. {
  99. if (e.Sender is TControl sender)
  100. {
  101. var logicalChildren = logicalChildrenSelector(sender);
  102. UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue);
  103. }
  104. });
  105. Control.TemplatedParentProperty.Changed.Subscribe(e =>
  106. {
  107. if (e.Sender is TControl sender)
  108. {
  109. var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl;
  110. logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent);
  111. }
  112. });
  113. TemplatedControl.TemplateProperty.Changed.Subscribe(e =>
  114. {
  115. if (e.Sender is TControl sender)
  116. {
  117. if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription))
  118. {
  119. subscription.Dispose();
  120. subscriptions.Value.Remove(sender);
  121. }
  122. }
  123. });
  124. }
  125. private static void UpdateLogicalChild(
  126. IControl control,
  127. IAvaloniaList<ILogical> logicalChildren,
  128. object oldValue,
  129. object newValue)
  130. {
  131. if (oldValue != newValue)
  132. {
  133. if (oldValue is IControl child)
  134. {
  135. logicalChildren.Remove(child);
  136. ((ISetInheritanceParent)child).SetParent(child.Parent);
  137. }
  138. child = newValue as IControl;
  139. if (child != null && !logicalChildren.Contains(child))
  140. {
  141. child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent);
  142. logicalChildren.Add(child);
  143. }
  144. }
  145. }
  146. }
  147. }