|
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Controls.Internals;
namespace Microsoft.Maui.Controls
{
/// <summary>Provides the base class for all Microsoft.Maui.Controls hierarchal elements.
/// This class contains all the methods and properties required to represent an element in the Microsoft.Maui.Controls hierarchy.
/// </summary>
/// <remarks>
/// <para>Important categories of visual elements are noted in the following table:</para>
/// <list type = "table" >
/// <listheader>
/// <term > Class </term >
/// <description > Description </description >
/// </listheader >
/// <item>
/// <term><see cref="VisualElement" /></term>
/// <description>An <see cref="Element" /> that occupies an area on the screen, has a visual appearance, and can obtain touch input.</description>
/// </item>
/// <item>
/// <term><see cref = "Cell"/></term>
/// <description> Cells are elements meant to be added to <see cref="ListView"/> or <see cref="TableView"/>.</description>
/// </item>
/// <item>
/// <term><see cref = "Page"/>
/// </term>
/// <description> A <see cref= "VisualElement"/> that occupies most or all of the screen and contains a single child.</description>
/// </item>
/// <item>
/// <term><see cref = "Layout"/></term>
/// <description><see cref= "Layout"/> have a single child of type <see cref="View" />, while subclasses of <see cref = "Layout"/> have a collection of multiple children views, including other layouts.</description>
/// </item>
/// <item>
/// <term> Controls and specialized <see cref="View" /></term>
/// <description>The lower part of the diagram shows the Microsoft.Maui.Controls classes for universally-available controls, such as <see cref = "Button"/> and <see cref="TableView"/>.</description>
/// </item>
/// </list>
///</remarks>
public abstract partial class Element : BindableObject, IElementDefinition, INameScope, IElementController, IVisualTreeElement, Maui.IElement, IEffectControlProvider, IToolTipElement, IContextFlyoutElement, IControlsElement, IHandlerDisconnectPolicies
{
internal static readonly ReadOnlyCollection<Element> EmptyChildren = new ReadOnlyCollection<Element>(Array.Empty<Element>());
/// <summary>Bindable property for <see cref="AutomationId"/>.</summary>
public static readonly BindableProperty AutomationIdProperty = BindableProperty.Create(nameof(AutomationId), typeof(string), typeof(Element), null);
/// <summary>Bindable property for <see cref="ClassId"/>.</summary>
public static readonly BindableProperty ClassIdProperty = BindableProperty.Create(nameof(ClassId), typeof(string), typeof(Element), null);
IList<BindableObject> _bindableResources;
List<Action<object, ResourcesChangedEventArgs>> _changeHandlers;
Dictionary<BindableProperty, (string, SetterSpecificity)> _dynamicResources;
IEffectControlProvider _effectControlProvider;
TrackableCollection<Effect> _effects;
Guid? _id;
WeakReference<Element> _parentOverride;
string _styleId;
IReadOnlyList<Element> _logicalChildrenReadonly;
IList<Element> _internalChildren;
/// <summary>Gets or sets a value that allows the automation framework to find and interact with this element.</summary>
/// <value>A value that the automation framework can use to find and interact with this element.</value>
/// <remarks>This value may only be set once on an element.</remarks>
public string AutomationId
{
get { return (string)GetValue(AutomationIdProperty); }
set
{
if (AutomationId != null)
throw new InvalidOperationException($"{nameof(AutomationId)} may only be set one time.");
SetValue(AutomationIdProperty, value);
}
}
/// <summary>Gets or sets a value used to identify a collection of semantically similar elements.</summary>
/// <value>A string that represents the collection the element belongs to.</value>
/// <remarks>Use the class id property to collect together elements into semantically similar groups for identification in UI testing and in theme engines.</remarks>
public string ClassId
{
get => (string)GetValue(ClassIdProperty);
set => SetValue(ClassIdProperty, value);
}
/// <summary>Gets or sets the styles and properties that will be applied to the element during runtime.</summary>
/// <value>A collection containing the different <see cref="Effect"/> to be applied to the element.</value>
public IList<Effect> Effects
{
get
{
if (_effects == null)
{
_effects = new TrackableCollection<Effect>();
_effects.CollectionChanged += EffectsOnCollectionChanged;
_effects.Clearing += EffectsOnClearing;
}
return _effects;
}
}
/// <summary>Gets a value that can be used to uniquely identify an element throughout the run of your application.</summary>
/// <value>A <see cref="Guid"/> uniquely identifying the element.</value>
/// <remarks>This value is generated at runtime and is not stable across different runs.</remarks>
/// <seealso cref="StyleId"/>
public Guid Id
{
get
{
if (!_id.HasValue)
_id = Guid.NewGuid();
return _id.Value;
}
}
/// <summary>Gets or sets a user defined value to uniquely identify the element.</summary>
/// <value>A string uniquely identifying the element.</value>
/// <remarks>Use the <see cref="StyleId"/> property to identify individual elements in your application for identification in UI testing and in theme engines.</remarks>
public string StyleId
{
get { return _styleId; }
set
{
if (_styleId == value)
return;
OnPropertyChanging();
_styleId = value;
OnPropertyChanged();
}
}
// Leaving this internal for now.
// If users want to add/remove from this they can use
// AddLogicalChildren and RemoveLogicalChildren on the respective control
// if available.
//
// Ultimately I don't think we'll need these to be virtual but some controls (layout)
// are going to take a more focused effort so I'd rather just do that in a
// separate PR. I don't think there's ever a scenario where a subclass needs
// to replace the backing store.
// If everyone just uses AddLogicalChildren and RemoveLogicalChildren
// and then overrides OnChildAdded/OnChildRemoved
// that should be sufficient
internal IReadOnlyList<Element> LogicalChildrenInternal
{
get
{
SetupChildren();
return _logicalChildrenReadonly;
}
}
private protected virtual IList<Element> LogicalChildrenInternalBackingStore
{
get
{
_internalChildren ??= new List<Element>();
return _internalChildren;
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Do not use! This is to be removed! Just used by Hot Reload! To be replaced with IVisualTreeElement!")]
public ReadOnlyCollection<Element> LogicalChildren =>
new ReadOnlyCollection<Element>(new TemporaryWrapper(LogicalChildrenInternal));
/// <inheritdoc/>
IReadOnlyList<Element> IElementController.LogicalChildren => LogicalChildrenInternal;
void SetupChildren()
{
_logicalChildrenReadonly ??= new ReadOnlyCollection<Element>(LogicalChildrenInternalBackingStore);
}
/// <summary>
/// Inserts an <see cref="Element"/> to the logical children at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which <see cref="Element"/> should be inserted.</param>
/// <param name="element">The <see cref="Element"/> to insert into the logical children.</param>
public void InsertLogicalChild(int index, Element element)
{
if (element is null)
{
return;
}
SetupChildren();
LogicalChildrenInternalBackingStore.Insert(index, element);
OnChildAdded(element);
}
/// <summary>
/// Adds an <see cref="Element"/> to the logical children.
/// </summary>
/// <param name="element">The <see cref="Element"/> to add to the logical children.</param>
public void AddLogicalChild(Element element)
{
if (element is null)
{
return;
}
SetupChildren();
LogicalChildrenInternalBackingStore.Add(element);
OnChildAdded(element);
}
/// <summary>
/// Removes the first occurrence of a specific <see cref="Element"/> from the logical children.
/// </summary>
/// <param name="element">The <see cref="Element"/> to remove.</param>
/// <returns>
/// true if item was successfully removed from the logical children;
/// otherwise, false. This method also returns false if <see cref="Element"/> is not found.
/// </returns>
public bool RemoveLogicalChild(Element element)
{
if (element is null)
{
return false;
}
if (LogicalChildrenInternalBackingStore is null)
return false;
var index = LogicalChildrenInternalBackingStore.IndexOf(element);
if (index < 0)
return false;
RemoveLogicalChild(element, index);
return true;
}
/// <summary>
/// Removes all child <see cref="Element"/>s.
/// </summary>
public void ClearLogicalChildren()
{
if (LogicalChildrenInternalBackingStore is null)
return;
if (LogicalChildrenInternal == EmptyChildren)
return;
// Reverse for-loop, so children can be removed while iterating
for (int i = LogicalChildrenInternalBackingStore.Count - 1; i >= 0; i--)
{
RemoveLogicalChild(LogicalChildrenInternalBackingStore[i], i);
}
}
internal bool RemoveLogicalChild(Element element, int index)
{
LogicalChildrenInternalBackingStore.Remove(element);
OnChildRemoved(element, index);
return true;
}
internal bool Owned { get; set; }
internal Element ParentOverride
{
get
{
if (_parentOverride is null)
{
return null;
}
if (_parentOverride.TryGetTarget(out var parent))
{
return parent;
}
else
{
Application.Current?
.FindMauiContext()?
.CreateLogger<Element>()?
.LogWarning($"The ParentOverride on {this} has been Garbage Collected. This should never happen. Please log a bug: https://github.com/dotnet/maui");
}
return null;
}
set
{
if (ParentOverride == value)
return;
bool emitChange = Parent != value;
if (emitChange)
{
OnPropertyChanging(nameof(Parent));
if (value != null)
OnParentChangingCore(Parent, value);
else
OnParentChangingCore(Parent, RealParent);
}
if (value == null)
_parentOverride = null;
else
_parentOverride = new WeakReference<Element>(value);
if (emitChange)
{
OnPropertyChanged(nameof(Parent));
OnParentChangedCore();
}
}
}
WeakReference<Element> _realParent;
/// <summary>For internal use by .NET MAUI.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Element RealParent
{
get
{
if (_realParent is null)
{
return null;
}
if (_realParent.TryGetTarget(out var parent))
{
return parent;
}
else
{
Application.Current?
.FindMauiContext()?
.CreateLogger<Element>()?
.LogWarning($"The RealParent on {this} has been Garbage Collected. This should never happen. Please log a bug: https://github.com/dotnet/maui");
}
return null;
}
private set
{
if (value is null)
_realParent = null;
else
_realParent = new WeakReference<Element>(value);
}
}
Dictionary<BindableProperty, (string, SetterSpecificity)> DynamicResources => _dynamicResources ?? (_dynamicResources = new Dictionary<BindableProperty, (string, SetterSpecificity)>());
/// <inheritdoc/>
void IElementDefinition.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
{
_changeHandlers ??= new List<Action<object, ResourcesChangedEventArgs>>(2);
_changeHandlers.Add(onchanged);
}
/// <summary>Gets or sets the parent <see cref="Element"/> of this element.</summary>
/// <value>The <see cref="Element"/> which should be the parent of this element.</value>
/// <remarks>Most application authors will not need to set the parent element by hand.</remarks>
public Element Parent
{
get { return ParentOverride ?? RealParent; }
set => SetParent(value);
}
void SetParent(Element value)
{
Element realParent = RealParent;
if (realParent == value)
{
return;
}
OnPropertyChanging(nameof(Parent));
if (_parentOverride == null)
{
OnParentChangingCore(Parent, value);
}
if (realParent is IElementDefinition element)
{
element.RemoveResourcesChangedListener(OnParentResourcesChanged);
if (value != null && (element is Layout || element is IControlTemplated))
{
Application.Current?.FindMauiContext()?.CreateLogger<Element>()?.LogWarning($"{this} is already a child of {element}. Remove {this} from {element} before adding to {value}.");
}
}
RealParent = value;
if (RealParent != null)
{
OnParentResourcesChanged(RealParent.GetMergedResources());
((IElementDefinition)RealParent).AddResourcesChangedListener(OnParentResourcesChanged);
}
object context = value?.BindingContext;
if (value != null)
{
value.SetChildInheritedBindingContext(this, context);
}
else
{
SetInheritedBindingContext(this, null);
}
OnParentSet();
if (_parentOverride == null)
{
OnParentChangedCore();
}
OnPropertyChanged(nameof(Parent));
}
internal bool IsTemplateRoot { get; set; }
/// <inheritdoc/>
void IElementDefinition.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
{
if (_changeHandlers == null)
return;
_changeHandlers.Remove(onchanged);
}
/// <summary>For internal use by .NET MAUI.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IEffectControlProvider EffectControlProvider
{
get { return _effectControlProvider; }
set
{
if (_effectControlProvider == value)
return;
if (_effectControlProvider != null && _effects != null)
{
foreach (Effect effect in _effects)
effect?.SendDetached();
}
_effectControlProvider = value;
if (_effectControlProvider != null && _effects != null)
{
foreach (Effect effect in _effects)
{
if (effect != null)
AttachEffect(effect);
}
}
}
}
/// <summary>For internal use by .NET MAUI.</summary>
void IElementController.SetValueFromRenderer(BindableProperty property, object value) => SetValueFromRenderer(property, value);
/// <summary>For internal use by .NET MAUI.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetValueFromRenderer(BindableProperty property, object value)
{
SetValue(property, value, specificity: SetterSpecificity.FromHandler);
}
/// <summary>For internal use by .NET MAUI.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetValueFromRenderer(BindablePropertyKey property, object value)
{
SetValue(property, value, specificity: SetterSpecificity.FromHandler);
}
/// <summary>For internal use by .NET MAUI.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool EffectIsAttached(string name)
{
foreach (var effect in Effects)
{
if (effect.ResolveId == name)
return true;
}
return false;
}
//this is only used by XAMLC, not added to public API
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable RS0016 // Add public types and members to the declared API
public INameScope transientNamescope;
#pragma warning restore RS0016 // Add public types and members to the declared API
/// <summary>Returns the element that has the specified name.</summary>
/// <param name="name">The name of the element to be found.</param>
/// <returns>The element that has the specified name.</returns>
/// <exception cref="InvalidOperationException">Thrown if the element's namescope couldn't be found.</exception>
public object FindByName(string name)
{
var namescope = GetNameScope() ?? transientNamescope;
if (namescope == null)
throw new InvalidOperationException("this element is not in a namescope");
return namescope.FindByName(name);
}
/// <inheritdoc/>
void INameScope.RegisterName(string name, object scopedElement)
{
var namescope = GetNameScope() ?? throw new InvalidOperationException("this element is not in a namescope");
namescope.RegisterName(name, scopedElement);
}
/// <inheritdoc/>
void INameScope.UnregisterName(string name)
{
var namescope = GetNameScope() ?? throw new InvalidOperationException("this element is not in a namescope");
namescope.UnregisterName(name);
}
/// <summary>Raised whenever a child element is added to the element.</summary>
public event EventHandler<ElementEventArgs> ChildAdded;
/// <summary>Raised whenever a child element is removed from the element.</summary>
public event EventHandler<ElementEventArgs> ChildRemoved;
/// <summary>Raised whenever a child element is added to the element's subtree.</summary>
public event EventHandler<ElementEventArgs> DescendantAdded;
/// <summary>Raised whenever a child element is removed from the elements subtree.</summary>
public event EventHandler<ElementEventArgs> DescendantRemoved;
/// <summary>Removes a previously set dynamic resource.</summary>
/// <param name="property">The <see cref="BindableProperty"/> from which to remove the DynamicResource.</param>
public new void RemoveDynamicResource(BindableProperty property)
{
base.RemoveDynamicResource(property);
}
/// <summary>Sets the <see cref="BindableProperty"/> property of this element to be updated via the DynamicResource with the provided key.</summary>
/// <param name="property">The property to be updated.</param>
/// <param name="key">The key for the requested resource.</param>
public new void SetDynamicResource(BindableProperty property, string key)
{
base.SetDynamicResource(property, key);
}
/// <inheritdoc/>
IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren()
=> LogicalChildrenInternal;
/// <inheritdoc/>
IVisualTreeElement IVisualTreeElement.GetVisualParent() => this.Parent;
/// <summary>Invoked whenever the binding context of the element changes. Implement this method to add class handling for this event.</summary>
/// <remarks>Implementors must call the base method.</remarks>
protected override void OnBindingContextChanged()
{
this.PropagateBindingContext(LogicalChildrenInternal, (child, bc) =>
{
SetChildInheritedBindingContext((Element)child, bc);
});
if (_bindableResources != null)
foreach (BindableObject item in _bindableResources)
{
SetInheritedBindingContext(item, BindingContext);
}
base.OnBindingContextChanged();
}
/// <summary>Raises the <see cref="ChildAdded"/> event. Implement this method to add class handling for this event.</summary>
/// <remarks>This method has no default implementation. You should still call the base implementation in case an intermediate class has implemented this method.</remarks>
/// <param name="child">The element that's been added as a child.</param>
protected virtual void OnChildAdded(Element child)
{
child.SetParent(this);
ChildAdded?.Invoke(this, new ElementEventArgs(child));
VisualDiagnostics.OnChildAdded(this, child);
OnDescendantAdded(child);
foreach (Element element in child.Descendants())
OnDescendantAdded(element);
}
/// <summary> Raises the <see cref="ChildRemoved"/> event. Implement this method to add class handling for this event </summary>
/// <remarks>
/// This method has no default implementation. You should still call the base implementation in case an intermediate class has implemented this method.
/// If not debugging, the logical tree index will not have any effect.
/// </remarks>
/// <param name="child">The child element that's been removed.</param>
/// <param name="oldLogicalIndex">The child's element index in the logical tree.</param>
protected virtual void OnChildRemoved(Element child, int oldLogicalIndex)
{
child.SetParent(null);
ChildRemoved?.Invoke(this, new ElementEventArgs(child));
VisualDiagnostics.OnChildRemoved(this, child, oldLogicalIndex);
OnDescendantRemoved(child);
foreach (Element element in child.Descendants())
OnDescendantRemoved(element);
}
/// <summary>Raises the (internal) <c>ParentSet</c> event. Implement this method in order to add behavior when the element is added to a parent.</summary>
/// <remarks>Implementors must call the base method.</remarks>
protected virtual void OnParentSet()
{
ParentSet?.Invoke(this, EventArgs.Empty);
ApplyStyleSheets();
(this as IPropertyPropagationController)?.PropagatePropertyChanged(null);
}
HashSet<string> _pendingHandlerUpdatesFromBPSet = new HashSet<string>();
private protected override void OnBindablePropertySet(BindableProperty property, object original, object value, bool changed, bool willFirePropertyChanged)
{
if (willFirePropertyChanged)
{
_pendingHandlerUpdatesFromBPSet.Add(property.PropertyName);
}
base.OnBindablePropertySet(property, original, value, changed, willFirePropertyChanged);
_pendingHandlerUpdatesFromBPSet.Remove(property.PropertyName);
UpdateHandlerValue(property.PropertyName, changed);
}
/// <summary>Method that is called when a bound property is changed.</summary>
/// <param name="propertyName">The name of the bound property that changed.</param>
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// If OnPropertyChanged is being called from a SetValue call on the BO we want the handler update to happen after
// the PropertyChanged Delegate on the BP and this OnPropertyChanged has fired.
// if you look at BO you'll see the order is this
//
// OnPropertyChanged(property.PropertyName);
// property.PropertyChanged?.Invoke(this, original, value);
//
// It can cause somewhat confusing behavior if the handler update happens between these two calls
// And the user has placed reacting code inside the BP.PropertyChanged callback
//
// If the OnPropertyChanged is being called from user code, we still want that to propagate to the mapper
bool waitForHandlerUpdateToFireFromBP = _pendingHandlerUpdatesFromBPSet.Contains(propertyName);
base.OnPropertyChanged(propertyName);
if (_effects?.Count > 0)
{
var args = new PropertyChangedEventArgs(propertyName);
foreach (Effect effect in _effects)
{
effect?.SendOnElementPropertyChanged(args);
}
}
if (!waitForHandlerUpdateToFireFromBP)
{
UpdateHandlerValue(propertyName, true);
}
}
private protected virtual void UpdateHandlerValue(string property, bool valueChanged)
{
if (valueChanged)
{
Handler?.UpdateValue(property);
}
}
internal IEnumerable<Element> Descendants() =>
Descendants<Element>();
/// <inheritdoc/>
IEnumerable<Element> IElementController.Descendants() =>
Descendants<Element>();
internal IEnumerable<TElement> Descendants<TElement>()
where TElement : Element
{
var queue = new Queue<Element>(16);
queue.Enqueue(this);
while (queue.Count > 0)
{
IReadOnlyList<Element> children = queue.Dequeue().LogicalChildrenInternal;
for (var i = 0; i < children.Count; i++)
{
Element child = children[i];
if (child is not TElement childT)
continue;
yield return childT;
queue.Enqueue(child);
}
}
}
internal virtual void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
if (e == ResourcesChangedEventArgs.StyleSheets)
ApplyStyleSheets();
else
OnParentResourcesChanged(e.Values);
}
internal virtual void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
{
OnResourcesChanged(values);
}
internal override void OnRemoveDynamicResource(BindableProperty property)
{
DynamicResources.Remove(property);
if (DynamicResources.Count == 0)
_dynamicResources = null;
base.OnRemoveDynamicResource(property);
}
internal virtual void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
if (e == ResourcesChangedEventArgs.StyleSheets)
ApplyStyleSheets();
else
OnResourcesChanged(e.Values);
}
internal void OnResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
{
if (values == null)
return;
if (_changeHandlers != null)
foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers.ToList())
handler(this, new ResourcesChangedEventArgs(values));
if (_dynamicResources == null)
return;
if (_bindableResources == null)
_bindableResources = new List<BindableObject>();
foreach (KeyValuePair<string, object> value in values)
{
List<(BindableProperty, SetterSpecificity)> changedResources = null;
foreach (KeyValuePair<BindableProperty, (string, SetterSpecificity)> dynR in DynamicResources)
{
// when the DynamicResource bound to a BindableProperty is
// changing then the BindableProperty needs to be refreshed;
// The .Value.Item1 is the name of DynamicResouce to which the BindableProperty is bound.
// The .Key is the name of the DynamicResource whose value is changing.
if (dynR.Value.Item1 != value.Key)
continue;
changedResources = changedResources ?? new List<(BindableProperty, SetterSpecificity)>();
changedResources.Add((dynR.Key, dynR.Value.Item2));
}
if (changedResources == null)
continue;
foreach ((BindableProperty, SetterSpecificity) changedResource in changedResources)
OnResourceChanged(changedResource.Item1, value.Value, changedResource.Item2);
var bindableObject = value.Value as BindableObject;
if (bindableObject != null && (bindableObject as Element)?.Parent == null)
{
if (!_bindableResources.Contains(bindableObject))
_bindableResources.Add(bindableObject);
SetInheritedBindingContext(bindableObject, BindingContext);
}
}
}
internal override void OnSetDynamicResource(BindableProperty property, string key, SetterSpecificity specificity)
{
base.OnSetDynamicResource(property, key, specificity);
if (!DynamicResources.TryGetValue(property, out var existing) || existing.Item2 <= specificity)
DynamicResources[property] = (key, specificity);
if (this.TryGetResource(key, out var value))
OnResourceChanged(property, value, specificity);
}
internal event EventHandler ParentSet;
internal virtual void SetChildInheritedBindingContext(Element child, object context)
{
SetInheritedBindingContext(child, context);
}
internal IEnumerable<Element> VisibleDescendants()
{
var queue = new Queue<Element>(16);
queue.Enqueue(this);
while (queue.Count > 0)
{
IReadOnlyList<Element> children = queue.Dequeue().LogicalChildrenInternal;
for (var i = 0; i < children.Count; i++)
{
var child = children[i] as VisualElement;
if (child == null || !child.IsVisible)
continue;
yield return child;
queue.Enqueue(child);
}
}
}
void AttachEffect(Effect effect)
{
if (_effectControlProvider == null)
return;
if (effect.IsAttached)
throw new InvalidOperationException("Cannot attach Effect to multiple sources");
Effect effectToRegister = effect;
if (effect is RoutingEffect re && re.Inner != null)
effectToRegister = re.Inner;
_effectControlProvider.RegisterEffect(effectToRegister);
effectToRegister.Element = this;
effect.SendAttached();
}
void EffectsOnClearing(object sender, EventArgs eventArgs)
{
foreach (Effect effect in _effects)
{
effect?.ClearEffect();
}
}
void EffectsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (Effect effect in e.NewItems)
{
AttachEffect(effect);
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
foreach (Effect effect in e.OldItems)
{
effect.ClearEffect();
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (Effect effect in e.NewItems)
{
AttachEffect(effect);
}
foreach (Effect effect in e.OldItems)
{
effect.ClearEffect();
}
break;
case NotifyCollectionChangedAction.Reset:
if (e.NewItems != null)
{
foreach (Effect effect in e.NewItems)
{
AttachEffect(effect);
}
}
if (e.OldItems != null)
{
foreach (Effect effect in e.OldItems)
{
effect.ClearEffect();
}
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
internal INameScope GetNameScope()
{
var element = this;
do
{
var ns = NameScope.GetNameScope(element);
if (ns != null)
return ns;
} while ((element = element.RealParent) != null);
return null;
}
void OnDescendantAdded(Element child)
{
DescendantAdded?.Invoke(this, new ElementEventArgs(child));
RealParent?.OnDescendantAdded(child);
}
void OnDescendantRemoved(Element child)
{
DescendantRemoved?.Invoke(this, new ElementEventArgs(child));
RealParent?.OnDescendantRemoved(child);
}
void OnResourceChanged(BindableProperty property, object value, SetterSpecificity specificity)
=> SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearTwoWayBindings, SetValuePrivateFlags.Default, specificity);
/// <summary>Raised whenever the element's starts to change.</summary>
public event EventHandler<ParentChangingEventArgs> ParentChanging;
/// <summary>Raised whenever the element's parent has changed.</summary>
public event EventHandler ParentChanged;
/// <summary>When overridden in a derived class, should raise the <see cref="ParentChanging"/> event.</summary>
/// <remarks>It is the implementor's responsibility to raise the <see cref="ParentChanging"/> event.</remarks>
/// <param name="args">Provides data for the <see cref="ParentChanging"/> event.</param>
protected virtual void OnParentChanging(ParentChangingEventArgs args) { }
/// <summary>When overridden in a derived class, should raise the <see cref="ParentChanged"/> event.</summary>
/// <remarks>It is the implementor's responsibility to raise the <see cref="ParentChanged"/> event.</remarks>
protected virtual void OnParentChanged() { }
private protected virtual void OnParentChangedCore()
{
ParentChanged?.Invoke(this, EventArgs.Empty);
OnParentChanged();
}
private protected virtual void OnParentChangingCore(Element oldParent, Element newParent)
{
if (oldParent == newParent)
return;
var args = new ParentChangingEventArgs(oldParent, newParent);
ParentChanging?.Invoke(this, args);
OnParentChanging(args);
}
IElementHandler _handler;
EffectsFactory _effectsFactory;
/// <inheritdoc/>
Maui.IElement Maui.IElement.Parent => Parent;
EffectsFactory EffectsFactory => _effectsFactory ??= Handler.MauiContext.Services.GetRequiredService<EffectsFactory>();
/// <summary>Gets or sets the associated handler for this element.</summary>
/// <value>An implementation of <see cref="IElementHandler"/>.</value>
/// <remarks>Maps the element to platform-specific controls and implementations.</remarks>
/// <seealso href="https://learn.microsoft.com/dotnet/maui/user-interface/handlers/">Conceptual documentation on handlers</seealso>
public IElementHandler Handler
{
get => _handler;
set => SetHandler(value);
}
/// <summary>Raised whenever the element's handler starts to change.</summary>
public event EventHandler<HandlerChangingEventArgs> HandlerChanging;
/// <summary>Raised whenever the element's handler has changed.</summary>
public event EventHandler HandlerChanged;
/// <summary>When overridden in a derived class, should raise the <see cref="HandlerChanging"/> event.</summary>
/// <remarks>It is the implementor's responsibility to raise the <see cref="HandlerChanging"/> event.</remarks>
/// <param name="args">Provides data for the <see cref="HandlerChanging"/> event.</param>
protected virtual void OnHandlerChanging(HandlerChangingEventArgs args) { }
/// <summary>When overridden in a derived class, should raise the <see cref="HandlerChanged"/> event.</summary>
/// <remarks>It is the implementor's responsibility to raise the <see cref="HandlerChanged"/> event.</remarks>
protected virtual void OnHandlerChanged() { }
private protected virtual void OnHandlerChangedCore()
{
EffectControlProvider = (Handler != null) ? this : null;
HandlerChanged?.Invoke(this, EventArgs.Empty);
OnHandlerChanged();
}
private protected virtual void OnHandlerChangingCore(HandlerChangingEventArgs args)
{
HandlerChanging?.Invoke(this, args);
OnHandlerChanging(args);
}
IElementHandler _previousHandler;
void SetHandler(IElementHandler newHandler)
{
if (newHandler == _handler)
return;
try
{
// If a handler is getting changed before the end of this method
// Something is wired up incorrectly
if (_previousHandler != null)
throw new InvalidOperationException("Handler is already being set elsewhere");
_previousHandler = _handler;
OnHandlerChangingCore(new HandlerChangingEventArgs(_previousHandler, newHandler));
_handler = newHandler;
// Only call disconnect if the previous handler is still connected to this virtual view.
// If a handler is being reused for a different VirtualView then the virtual
// view would have already rolled
if (_previousHandler?.VirtualView == this)
_previousHandler?.DisconnectHandler();
if (_handler?.VirtualView != this)
_handler?.SetVirtualView(this);
OnHandlerChangedCore();
}
finally
{
_previousHandler = null;
}
}
/// <summary>
/// Registers the specified <paramref name="effect"/> to this element.
/// </summary>
/// <param name="effect">The effect to be registered.</param>
void IEffectControlProvider.RegisterEffect(Effect effect)
{
if (effect is RoutingEffect re && re.Inner != null)
{
re.Element = this;
re.Inner.Element = this;
return;
}
var platformEffect = EffectsFactory.CreateEffect(effect);
if (platformEffect != null)
{
platformEffect.Element = this;
effect.PlatformEffect = platformEffect;
}
else
{
effect.Element = this;
}
}
/// <inheritdoc/>
ToolTip IToolTipElement.ToolTip => ToolTipProperties.GetToolTip(this);
/// <inheritdoc/>
IFlyout IContextFlyoutElement.ContextFlyout => FlyoutBase.GetContextFlyout(this);
HandlerDisconnectPolicy IHandlerDisconnectPolicies.DisconnectPolicy
{
get => HandlerProperties.GetDisconnectPolicy(this);
set => HandlerProperties.SetDisconnectPolicy(this, value);
}
class TemporaryWrapper : IList<Element>
{
IReadOnlyList<Element> _inner;
public TemporaryWrapper(IReadOnlyList<Element> inner)
{
_inner = inner;
}
Element IList<Element>.this[int index] { get => _inner[index]; set => throw new NotSupportedException(); }
int ICollection<Element>.Count => _inner.Count;
bool ICollection<Element>.IsReadOnly => true;
void ICollection<Element>.Add(Element item) => throw new NotSupportedException();
void ICollection<Element>.Clear() => throw new NotSupportedException();
bool ICollection<Element>.Contains(Element item) => _inner.IndexOf(item) != -1;
void ICollection<Element>.CopyTo(Element[] array, int arrayIndex) => throw new NotSupportedException();
IEnumerator<Element> IEnumerable<Element>.GetEnumerator() => _inner.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _inner.GetEnumerator();
int IList<Element>.IndexOf(Element item) => _inner.IndexOf(item);
void IList<Element>.Insert(int index, Element item) => throw new NotSupportedException();
bool ICollection<Element>.Remove(Element item) => throw new NotSupportedException();
void IList<Element>.RemoveAt(int index) => throw new NotSupportedException();
}
}
}
|