ContentControlMixin.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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.Runtime.CompilerServices;
  6. using Avalonia.Collections;
  7. using Avalonia.Controls.Presenters;
  8. using Avalonia.Controls.Primitives;
  9. using Avalonia.Interactivity;
  10. using Avalonia.LogicalTree;
  11. using Avalonia.Styling;
  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. /// updates keeps the control's logical children in sync with the content being displayed by
  21. /// the 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 TemplateApplied(object s, RoutedEventArgs ev)
  49. {
  50. if (s is TControl sender)
  51. {
  52. var e = (TemplateAppliedEventArgs)ev;
  53. var presenter = (IControl)e.NameScope.Find(presenterName);
  54. if (presenter != null)
  55. {
  56. presenter.ApplyTemplate();
  57. var logicalChildren = logicalChildrenSelector(sender);
  58. var subscription = presenter
  59. .GetPropertyChangedObservable(ContentPresenter.ChildProperty)
  60. .Subscribe(c => UpdateLogicalChild(
  61. sender,
  62. logicalChildren,
  63. c.OldValue,
  64. c.NewValue));
  65. UpdateLogicalChild(
  66. sender,
  67. logicalChildren,
  68. null,
  69. presenter.GetValue(ContentPresenter.ChildProperty));
  70. subscriptions.Value.Add(sender, subscription);
  71. }
  72. }
  73. }
  74. TemplatedControl.TemplateAppliedEvent.AddClassHandler(
  75. typeof(TControl),
  76. TemplateApplied,
  77. RoutingStrategies.Direct);
  78. content.Changed.Subscribe(e =>
  79. {
  80. if (e.Sender is TControl sender)
  81. {
  82. var logicalChildren = logicalChildrenSelector(sender);
  83. UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue);
  84. }
  85. });
  86. Control.TemplatedParentProperty.Changed.Subscribe(e =>
  87. {
  88. if (e.Sender is TControl sender)
  89. {
  90. var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl;
  91. logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent);
  92. }
  93. });
  94. TemplatedControl.TemplateProperty.Changed.Subscribe(e =>
  95. {
  96. if (e.Sender is TControl sender)
  97. {
  98. if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription))
  99. {
  100. subscription.Dispose();
  101. subscriptions.Value.Remove(sender);
  102. }
  103. }
  104. });
  105. }
  106. private static void UpdateLogicalChild(
  107. IControl control,
  108. IAvaloniaList<ILogical> logicalChildren,
  109. object oldValue,
  110. object newValue)
  111. {
  112. if (oldValue != newValue)
  113. {
  114. if (oldValue is IControl child)
  115. {
  116. logicalChildren.Remove(child);
  117. }
  118. child = newValue as IControl;
  119. if (child != null && !logicalChildren.Contains(child))
  120. {
  121. child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent);
  122. logicalChildren.Add(child);
  123. }
  124. }
  125. }
  126. }
  127. }