NativeControlHost.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using Avalonia.Controls.Platform;
  5. using Avalonia.Platform;
  6. using Avalonia.Threading;
  7. using Avalonia.VisualTree;
  8. namespace Avalonia.Controls
  9. {
  10. public class NativeControlHost : Control
  11. {
  12. private TopLevel? _currentRoot;
  13. private INativeControlHostImpl? _currentHost;
  14. private INativeControlHostControlTopLevelAttachment? _attachment;
  15. private IPlatformHandle? _nativeControlHandle;
  16. private bool _queuedForDestruction;
  17. private bool _queuedForMoveResize;
  18. private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
  19. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  20. {
  21. _currentRoot = e.Root as TopLevel;
  22. var visual = (Visual)this;
  23. while (visual != null)
  24. {
  25. if (visual is Visual v)
  26. {
  27. v.PropertyChanged += PropertyChangedHandler;
  28. _propertyChangedSubscriptions.Add(v);
  29. }
  30. visual = visual.GetVisualParent();
  31. }
  32. UpdateHost();
  33. }
  34. private void PropertyChangedHandler(object? sender, AvaloniaPropertyChangedEventArgs e)
  35. {
  36. if (e.IsEffectiveValueChange && (e.Property == BoundsProperty || e.Property == IsVisibleProperty))
  37. EnqueueForMoveResize();
  38. }
  39. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  40. {
  41. _currentRoot = null;
  42. if (_propertyChangedSubscriptions != null)
  43. {
  44. foreach (var v in _propertyChangedSubscriptions)
  45. v.PropertyChanged -= PropertyChangedHandler;
  46. _propertyChangedSubscriptions.Clear();
  47. }
  48. UpdateHost();
  49. }
  50. private void UpdateHost()
  51. {
  52. _queuedForMoveResize = false;
  53. _currentHost = _currentRoot?.PlatformImpl?.TryGetFeature<INativeControlHostImpl>();
  54. if (_currentHost != null)
  55. {
  56. // If there is an existing attachment, ensure that we are attached to the proper host or destroy the attachment
  57. if (_attachment != null && _attachment.AttachedTo != _currentHost)
  58. {
  59. if (_attachment != null)
  60. {
  61. if (_attachment.IsCompatibleWith(_currentHost))
  62. {
  63. _attachment.AttachedTo = _currentHost;
  64. }
  65. else
  66. {
  67. _attachment.Dispose();
  68. _attachment = null;
  69. }
  70. }
  71. }
  72. // If there is no attachment, but the control exists,
  73. // attempt to attach to to the current toplevel or destroy the control if it's incompatible
  74. if (_attachment == null && _nativeControlHandle != null)
  75. {
  76. if (_currentHost.IsCompatibleWith(_nativeControlHandle))
  77. _attachment = _currentHost.CreateNewAttachment(_nativeControlHandle);
  78. else
  79. DestroyNativeControl();
  80. }
  81. // There is no control handle an no attachment, create both
  82. if (_nativeControlHandle == null)
  83. {
  84. _attachment = _currentHost.CreateNewAttachment(parent =>
  85. _nativeControlHandle = CreateNativeControlCore(parent));
  86. }
  87. }
  88. else
  89. {
  90. // Immediately detach the control from the current toplevel if there is an existing attachment
  91. if (_attachment != null)
  92. _attachment.AttachedTo = null;
  93. // Don't destroy the control immediately, it might be just being reparented to another TopLevel
  94. if (_nativeControlHandle != null && !_queuedForDestruction)
  95. {
  96. _queuedForDestruction = true;
  97. Dispatcher.UIThread.Post(CheckDestruction, DispatcherPriority.Background);
  98. }
  99. }
  100. if (_attachment?.AttachedTo != _currentHost)
  101. return;
  102. TryUpdateNativeControlPosition();
  103. }
  104. private Rect? GetAbsoluteBounds()
  105. {
  106. Debug.Assert(_currentRoot is not null);
  107. var bounds = Bounds;
  108. var position = this.TranslatePoint(default, _currentRoot);
  109. if (position == null)
  110. return null;
  111. return new Rect(position.Value, bounds.Size);
  112. }
  113. void EnqueueForMoveResize()
  114. {
  115. if(_queuedForMoveResize)
  116. return;
  117. _queuedForMoveResize = true;
  118. Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render);
  119. }
  120. public bool TryUpdateNativeControlPosition()
  121. {
  122. if (_currentHost == null)
  123. return false;
  124. var bounds = GetAbsoluteBounds();
  125. if (IsEffectivelyVisible && bounds.HasValue)
  126. {
  127. if (bounds.Value.IsDefault)
  128. return false;
  129. _attachment?.ShowInBounds(bounds.Value);
  130. }
  131. else
  132. _attachment?.HideWithSize(Bounds.Size);
  133. return true;
  134. }
  135. private void CheckDestruction()
  136. {
  137. _queuedForDestruction = false;
  138. if (_currentRoot == null)
  139. DestroyNativeControl();
  140. }
  141. protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
  142. {
  143. if (_currentHost == null)
  144. throw new InvalidOperationException();
  145. return _currentHost.CreateDefaultChild(parent);
  146. }
  147. private void DestroyNativeControl()
  148. {
  149. if (_nativeControlHandle != null)
  150. {
  151. _attachment?.Dispose();
  152. _attachment = null;
  153. DestroyNativeControlCore(_nativeControlHandle);
  154. _nativeControlHandle = null;
  155. }
  156. }
  157. protected virtual void DestroyNativeControlCore(IPlatformHandle control)
  158. {
  159. ((INativeControlHostDestroyableControlHandle)control).Destroy();
  160. }
  161. }
  162. }