627 строки
16 KiB
C#
627 строки
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
using Xamarin.Forms.Internals;
|
|
|
|
namespace Xamarin.Forms
|
|
{
|
|
public abstract partial class Element : BindableObject, IElement, INameScope, IElementController
|
|
{
|
|
|
|
public static readonly BindableProperty MenuProperty = BindableProperty.CreateAttached(nameof(Menu), typeof(Menu), typeof(Element), null);
|
|
|
|
public static Menu GetMenu(BindableObject bindable)
|
|
{
|
|
return (Menu)bindable.GetValue(MenuProperty);
|
|
}
|
|
|
|
public static void SetMenu(BindableObject bindable, Menu menu)
|
|
{
|
|
bindable.SetValue(MenuProperty, menu);
|
|
}
|
|
|
|
internal static readonly ReadOnlyCollection<Element> EmptyChildren = new ReadOnlyCollection<Element>(new Element[0]);
|
|
|
|
public static readonly BindableProperty AutomationIdProperty = BindableProperty.Create(nameof(AutomationId), typeof(string), typeof(Element), null);
|
|
|
|
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> _dynamicResources;
|
|
|
|
IEffectControlProvider _effectControlProvider;
|
|
|
|
TrackableCollection<Effect> _effects;
|
|
|
|
Guid? _id;
|
|
|
|
Element _parentOverride;
|
|
|
|
IPlatform _platform;
|
|
|
|
string _styleId;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
public string ClassId
|
|
{
|
|
get { return (string)GetValue(ClassIdProperty); }
|
|
set { SetValue(ClassIdProperty, value); }
|
|
}
|
|
|
|
public IList<Effect> Effects
|
|
{
|
|
get
|
|
{
|
|
if (_effects == null)
|
|
{
|
|
_effects = new TrackableCollection<Effect>();
|
|
_effects.CollectionChanged += EffectsOnCollectionChanged;
|
|
_effects.Clearing += EffectsOnClearing;
|
|
}
|
|
return _effects;
|
|
}
|
|
}
|
|
|
|
public Guid Id
|
|
{
|
|
get
|
|
{
|
|
if (!_id.HasValue)
|
|
_id = Guid.NewGuid();
|
|
return _id.Value;
|
|
}
|
|
}
|
|
|
|
[Obsolete("ParentView is obsolete as of version 2.1.0. Please use Parent instead.")]
|
|
public VisualElement ParentView
|
|
{
|
|
get
|
|
{
|
|
Element parent = Parent;
|
|
while (parent != null)
|
|
{
|
|
var parentView = parent as VisualElement;
|
|
if (parentView != null)
|
|
return parentView;
|
|
parent = parent.RealParent;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public string StyleId
|
|
{
|
|
get { return _styleId; }
|
|
set
|
|
{
|
|
if (_styleId == value)
|
|
return;
|
|
|
|
OnPropertyChanging();
|
|
_styleId = value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
internal virtual ReadOnlyCollection<Element> LogicalChildrenInternal => EmptyChildren;
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public ReadOnlyCollection<Element> LogicalChildren => LogicalChildrenInternal;
|
|
|
|
internal bool Owned { get; set; }
|
|
|
|
internal Element ParentOverride
|
|
{
|
|
get { return _parentOverride; }
|
|
set
|
|
{
|
|
if (_parentOverride == value)
|
|
return;
|
|
|
|
bool emitChange = Parent != value;
|
|
|
|
if (emitChange)
|
|
OnPropertyChanging(nameof(Parent));
|
|
|
|
_parentOverride = value;
|
|
|
|
if (emitChange)
|
|
OnPropertyChanged(nameof(Parent));
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public IPlatform Platform
|
|
{
|
|
get
|
|
{
|
|
if (_platform == null && RealParent != null)
|
|
return RealParent.Platform;
|
|
return _platform;
|
|
}
|
|
set
|
|
{
|
|
if (_platform == value)
|
|
return;
|
|
_platform = value;
|
|
PlatformSet?.Invoke(this, EventArgs.Empty);
|
|
foreach (Element descendant in Descendants())
|
|
{
|
|
descendant._platform = _platform;
|
|
descendant.PlatformSet?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
// you're not my real dad
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public Element RealParent { get; private set; }
|
|
|
|
Dictionary<BindableProperty, string> DynamicResources
|
|
{
|
|
get { return _dynamicResources ?? (_dynamicResources = new Dictionary<BindableProperty, string>()); }
|
|
}
|
|
|
|
void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
|
|
{
|
|
_changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(2);
|
|
_changeHandlers.Add(onchanged);
|
|
}
|
|
|
|
public Element Parent
|
|
{
|
|
get { return _parentOverride ?? RealParent; }
|
|
set
|
|
{
|
|
if (RealParent == value)
|
|
return;
|
|
|
|
OnPropertyChanging();
|
|
|
|
if (RealParent != null)
|
|
((IElement)RealParent).RemoveResourcesChangedListener(OnParentResourcesChanged);
|
|
RealParent = value;
|
|
if (RealParent != null)
|
|
{
|
|
OnParentResourcesChanged(RealParent.GetMergedResources());
|
|
((IElement)RealParent).AddResourcesChangedListener(OnParentResourcesChanged);
|
|
}
|
|
|
|
object context = value != null ? value.BindingContext : null;
|
|
if (value != null)
|
|
{
|
|
value.SetChildInheritedBindingContext(this, context);
|
|
}
|
|
else
|
|
{
|
|
SetInheritedBindingContext(this, null);
|
|
}
|
|
|
|
OnParentSet();
|
|
|
|
if (RealParent != null)
|
|
{
|
|
IPlatform platform = RealParent.Platform;
|
|
if (platform != null)
|
|
Platform = platform;
|
|
}
|
|
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
|
|
{
|
|
if (_changeHandlers == null)
|
|
return;
|
|
_changeHandlers.Remove(onchanged);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IElementController.SetValueFromRenderer(BindableProperty property, object value) => SetValueFromRenderer(property, value);
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public void SetValueFromRenderer(BindableProperty property, object value)
|
|
{
|
|
SetValueCore(property, value);
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public void SetValueFromRenderer(BindablePropertyKey property, object value)
|
|
{
|
|
SetValueCore(property, value);
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public bool EffectIsAttached(string name)
|
|
{
|
|
foreach (var effect in Effects)
|
|
{
|
|
if (effect.ResolveId == name)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public object FindByName(string name)
|
|
{
|
|
var namescope = GetNameScope();
|
|
if (namescope == null)
|
|
throw new InvalidOperationException("this element is not in a namescope");
|
|
return namescope.FindByName(name);
|
|
}
|
|
|
|
void INameScope.RegisterName(string name, object scopedElement)
|
|
{
|
|
var namescope = GetNameScope();
|
|
if (namescope == null)
|
|
throw new InvalidOperationException("this element is not in a namescope");
|
|
namescope.RegisterName(name, scopedElement);
|
|
}
|
|
|
|
public event EventHandler<ElementEventArgs> ChildAdded;
|
|
|
|
public event EventHandler<ElementEventArgs> ChildRemoved;
|
|
|
|
public event EventHandler<ElementEventArgs> DescendantAdded;
|
|
|
|
public event EventHandler<ElementEventArgs> DescendantRemoved;
|
|
|
|
public new void RemoveDynamicResource(BindableProperty property)
|
|
{
|
|
base.RemoveDynamicResource(property);
|
|
}
|
|
|
|
public new void SetDynamicResource(BindableProperty property, string key)
|
|
{
|
|
base.SetDynamicResource(property, key);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
protected virtual void OnChildAdded(Element child)
|
|
{
|
|
child.Parent = this;
|
|
if (Platform != null)
|
|
child.Platform = Platform;
|
|
|
|
child.ApplyBindings(skipBindingContext: false, fromBindingContextChanged:true);
|
|
|
|
ChildAdded?.Invoke(this, new ElementEventArgs(child));
|
|
|
|
OnDescendantAdded(child);
|
|
foreach (Element element in child.Descendants())
|
|
OnDescendantAdded(element);
|
|
}
|
|
|
|
protected virtual void OnChildRemoved(Element child)
|
|
{
|
|
child.Parent = null;
|
|
|
|
ChildRemoved?.Invoke(child, new ElementEventArgs(child));
|
|
|
|
OnDescendantRemoved(child);
|
|
foreach (Element element in child.Descendants())
|
|
OnDescendantRemoved(element);
|
|
}
|
|
|
|
protected virtual void OnParentSet()
|
|
{
|
|
ParentSet?.Invoke(this, EventArgs.Empty);
|
|
ApplyStyleSheetsOnParentSet();
|
|
}
|
|
|
|
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
|
{
|
|
base.OnPropertyChanged(propertyName);
|
|
|
|
if (_effects == null || _effects.Count == 0)
|
|
return;
|
|
|
|
var args = new PropertyChangedEventArgs(propertyName);
|
|
foreach (Effect effect in _effects)
|
|
{
|
|
effect?.SendOnElementPropertyChanged(args);
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public IEnumerable<Element> Descendants()
|
|
{
|
|
var queue = new Queue<Element>(16);
|
|
queue.Enqueue(this);
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildrenInternal;
|
|
for (var i = 0; i < children.Count; i++)
|
|
{
|
|
Element child = children[i];
|
|
yield return child;
|
|
queue.Enqueue(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal virtual void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e)
|
|
{
|
|
if (e == ResourcesChangedEventArgs.StyleSheets)
|
|
ApplyStyleSheetsOnParentSet();
|
|
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)
|
|
{
|
|
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)
|
|
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> changedResources = null;
|
|
foreach (KeyValuePair<BindableProperty, string> dynR in DynamicResources)
|
|
{
|
|
// when the DynamicResource bound to a BindableProperty is
|
|
// changing then the BindableProperty needs to be refreshed;
|
|
// The .Value 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 != value.Key)
|
|
continue;
|
|
changedResources = changedResources ?? new List<BindableProperty>();
|
|
changedResources.Add(dynR.Key);
|
|
}
|
|
if (changedResources == null)
|
|
continue;
|
|
foreach (BindableProperty changedResource in changedResources)
|
|
OnResourceChanged(changedResource, value.Value);
|
|
|
|
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)
|
|
{
|
|
base.OnSetDynamicResource(property, key);
|
|
DynamicResources[property] = key;
|
|
object value;
|
|
if (this.TryGetResource(key, out value))
|
|
OnResourceChanged(property, value);
|
|
}
|
|
|
|
internal event EventHandler ParentSet;
|
|
|
|
internal static void SetFlowDirectionFromParent(Element child)
|
|
{
|
|
IFlowDirectionController controller = child as IFlowDirectionController;
|
|
if (controller == null)
|
|
return;
|
|
|
|
if (controller.EffectiveFlowDirection.IsImplicit())
|
|
{
|
|
var parentView = child.Parent as IFlowDirectionController;
|
|
if (parentView == null)
|
|
return;
|
|
|
|
var flowDirection = parentView.EffectiveFlowDirection.ToFlowDirection();
|
|
|
|
if (flowDirection != controller.EffectiveFlowDirection.ToFlowDirection())
|
|
{
|
|
controller.EffectiveFlowDirection = flowDirection.ToEffectiveFlowDirection();
|
|
}
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public event EventHandler PlatformSet;
|
|
|
|
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)
|
|
{
|
|
ReadOnlyCollection<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)
|
|
effectToRegister = ((RoutingEffect)effect).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)
|
|
{
|
|
SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearTwoWayBindings);
|
|
}
|
|
}
|
|
}
|