2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Diagnostics;
5 using System.Reflection;
6 using System.Runtime.CompilerServices;
7 using Tizen.NUI.Internals;
9 namespace Tizen.NUI.Binding
12 /// Provides a mechanism by which application developers can propagate changes that are made to data in one object to another, by enabling validation, type coercion, and an event system.
14 [EditorBrowsable(EditorBrowsableState.Never)]
15 public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
18 /// Implements the bound property whose interface is provided by the BindingContext property.
20 public static readonly BindableProperty BindingContextProperty =
21 BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object),
22 BindingMode.OneWay, null, BindingContextPropertyChanged, null, null, BindingContextPropertyBindingChanging);
24 readonly List<BindablePropertyContext> _properties = new List<BindablePropertyContext>(4);
27 object _inheritedContext;
30 /// Gets or sets object that contains the properties that will be targeted by the bound properties that belong to this BindableObject.
32 public object BindingContext
34 get { return _inheritedContext ?? GetValue(BindingContextProperty); }
35 set { SetValue(BindingContextProperty, value); }
38 void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key)
40 SetDynamicResource(property, key, false);
44 /// Raised when a property has changed.
46 public event PropertyChangedEventHandler PropertyChanged;
49 /// Raised whenever the BindingContext property changes.
51 public event EventHandler BindingContextChanged;
53 internal void ClearValue(BindableProperty property, bool fromStyle)
55 ClearValue(property, fromStyle: fromStyle, checkAccess: true);
59 /// Clears any value set by Xamarin.Forms.BindableObject.SetValue.
61 /// <param name="property">The BindableProperty to clear</param>
62 public void ClearValue(BindableProperty property)
64 ClearValue(property, fromStyle: false, checkAccess: true);
68 /// Clears any value set by Xamarin.Forms.BindableObject.SetValue for the property that is identified by propertyKey.
70 /// <param name="propertyKey">The BindablePropertyKey that identifies the BindableProperty to clear.</param>
71 public void ClearValue(BindablePropertyKey propertyKey)
73 if (propertyKey == null)
74 throw new ArgumentNullException("propertyKey");
76 ClearValue(propertyKey.BindableProperty, fromStyle:false, checkAccess: false);
80 /// Return true if the target property exists and has been set.
82 /// <param name="targetProperty">The target property</param>
83 /// <returns>return true if the target property exists and has been set</returns>
84 public bool IsSet(BindableProperty targetProperty)
86 if (targetProperty == null)
87 throw new ArgumentNullException(nameof(targetProperty));
89 var bpcontext = GetContext(targetProperty);
90 return bpcontext != null
91 && (bpcontext.Attributes & BindableContextAttributes.IsDefaultValue) == 0;
95 /// Returns the value that is contained the BindableProperty.
97 /// <param name="property">The BindableProperty for which to get the value.</param>
98 /// <returns>The value that is contained the BindableProperty</returns>
99 public object GetValue(BindableProperty property)
101 if (property == null)
102 throw new ArgumentNullException("property");
104 BindablePropertyContext context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
107 return property.DefaultValue;
109 return context.Value;
113 /// Raised when a property is about to change.
115 public event PropertyChangingEventHandler PropertyChanging;
118 /// Removes a previously set binding.
120 /// <param name="property">The BindableProperty from which to remove bindings.</param>
121 public void RemoveBinding(BindableProperty property)
123 if (property == null)
124 throw new ArgumentNullException("property");
126 BindablePropertyContext context = GetContext(property);
127 if (context == null || context.Binding == null)
130 RemoveBinding(property, context);
134 /// Assigns a binding to a property.
136 /// <param name="targetProperty">The BindableProperty on which to set a binding.</param>
137 /// <param name="binding">The binding to set.</param>
138 public void SetBinding(BindableProperty targetProperty, BindingBase binding)
140 SetBinding(targetProperty, binding, false);
144 /// Sets the value of the specified property.
146 /// <param name="property">The BindableProperty on which to assign a value.</param>
147 /// <param name="value">The value to set.</param>
148 public void SetValue(BindableProperty property, object value)
150 SetValue(property, value, false, true);
154 /// Sets the value of the propertyKey.
156 /// <param name="propertyKey">The BindablePropertyKey on which to assign a value.</param>
157 /// <param name="value">The value to set.</param>
158 public void SetValue(BindablePropertyKey propertyKey, object value)
160 if (propertyKey == null)
161 throw new ArgumentNullException("propertyKey");
163 SetValue(propertyKey.BindableProperty, value, false, false);
167 /// Set the inherited context to a neated element.
169 /// <param name="bindable">The object on which to set the inherited binding context.</param>
170 /// <param name="value">The inherited context to set.</param>
171 [EditorBrowsable(EditorBrowsableState.Never)]
172 public static void SetInheritedBindingContext(BindableObject bindable, object value)
174 BindablePropertyContext bpContext = bindable.GetContext(BindingContextProperty);
175 if (bpContext != null && ((bpContext.Attributes & BindableContextAttributes.IsManuallySet) != 0))
178 object oldContext = bindable._inheritedContext;
180 if (ReferenceEquals(oldContext, value))
183 if (bpContext != null && oldContext == null)
184 oldContext = bpContext.Value;
186 if (bpContext != null && bpContext.Binding != null)
188 bpContext.Binding.Context = value;
189 bindable._inheritedContext = null;
193 bindable._inheritedContext = value;
196 bindable.ApplyBindings(skipBindingContext:false, fromBindingContextChanged:true);
197 bindable.OnBindingContextChanged();
201 /// Apply the bindings to BindingContext.
203 protected void ApplyBindings()
205 ApplyBindings(skipBindingContext: false, fromBindingContextChanged: false);
209 /// Override this method to execute an action when the BindingContext changes.
211 protected virtual void OnBindingContextChanged()
213 BindingContextChanged?.Invoke(this, EventArgs.Empty);
217 /// Call this method from a child class to notify that a change happened on a property.
219 /// <param name="propertyName">The name of the property that changed.</param>
220 protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
221 => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
224 /// Call this method from a child class to notify that a change is going to happen on a property.
226 /// <param name="propertyName">The name of the property that is changing.</param>
227 protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null)
228 => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
231 /// Unapplies all previously set bindings.
233 protected void UnapplyBindings()
235 for (int i = 0, _propertiesCount = _properties.Count; i < _propertiesCount; i++) {
236 BindablePropertyContext context = _properties [i];
237 if (context.Binding == null)
240 context.Binding.Unapply();
244 internal bool GetIsBound(BindableProperty targetProperty)
246 if (targetProperty == null)
247 throw new ArgumentNullException("targetProperty");
249 BindablePropertyContext bpcontext = GetContext(targetProperty);
250 return bpcontext != null && bpcontext.Binding != null;
254 /// Returns the value that is contained the BindableProperty.
256 /// <param name="property0">The BindableProperty instance.</param>
257 /// <param name="property1">The BindableProperty instance.</param>
258 /// <returns>The value that is contained the BindableProperty</returns>
259 [EditorBrowsable(EditorBrowsableState.Never)]
260 public object[] GetValues(BindableProperty property0, BindableProperty property1)
262 var values = new object[2];
264 for (var i = 0; i < _properties.Count; i++)
266 BindablePropertyContext context = _properties[i];
268 if (ReferenceEquals(context.Property, property0))
270 values[0] = context.Value;
273 else if (ReferenceEquals(context.Property, property1))
275 values[1] = context.Value;
279 if (property0 == null && property1 == null)
283 if (!ReferenceEquals(property0, null))
284 values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
285 if (!ReferenceEquals(property1, null))
286 values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
292 /// Returns the value that is contained the BindableProperty.
294 /// <param name="property0">The BindableProperty instance.</param>
295 /// <param name="property1">The BindableProperty instance.</param>
296 /// <param name="property2">The BindableProperty instance.</param>
297 /// <returns>The value that is contained the BindableProperty</returns>
298 [EditorBrowsable(EditorBrowsableState.Never)]
299 public object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2)
301 var values = new object[3];
303 for (var i = 0; i < _properties.Count; i++)
305 BindablePropertyContext context = _properties[i];
307 if (ReferenceEquals(context.Property, property0))
309 values[0] = context.Value;
312 else if (ReferenceEquals(context.Property, property1))
314 values[1] = context.Value;
317 else if (ReferenceEquals(context.Property, property2))
319 values[2] = context.Value;
323 if (property0 == null && property1 == null && property2 == null)
327 if (!ReferenceEquals(property0, null))
328 values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
329 if (!ReferenceEquals(property1, null))
330 values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
331 if (!ReferenceEquals(property2, null))
332 values[2] = property2.DefaultValueCreator == null ? property2.DefaultValue : CreateAndAddContext(property2).Value;
338 /// Returns the value that is contained the BindableProperty.
340 /// <param name="properties">The array of the BindableProperty instances</param>
341 /// <returns>The values that is contained the BindableProperty instances.</returns>
342 [EditorBrowsable(EditorBrowsableState.Never)]
343 internal object[] GetValues(params BindableProperty[] properties)
345 var values = new object[properties.Length];
346 for (var i = 0; i < _properties.Count; i++) {
347 var context = _properties[i];
348 var index = properties.IndexOf(context.Property);
351 values[index] = context.Value;
353 for (var i = 0; i < values.Length; i++) {
354 if (!ReferenceEquals(values[i], null))
356 values[i] = properties[i].DefaultValueCreator == null ? properties[i].DefaultValue : CreateAndAddContext(properties[i]).Value;
361 internal virtual void OnRemoveDynamicResource(BindableProperty property)
365 internal virtual void OnSetDynamicResource(BindableProperty property, string key)
369 internal void RemoveDynamicResource(BindableProperty property)
371 if (property == null)
372 throw new ArgumentNullException("property");
374 OnRemoveDynamicResource(property);
375 BindablePropertyContext context = GetOrCreateContext(property);
376 context.Attributes &= ~BindableContextAttributes.IsDynamicResource;
379 internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle)
381 if (targetProperty == null)
382 throw new ArgumentNullException("targetProperty");
384 throw new ArgumentNullException("binding");
386 if (fromStyle && !CanBeSetFromStyle(targetProperty))
389 var context = GetOrCreateContext(targetProperty);
391 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
393 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
395 if (context.Binding != null)
396 context.Binding.Unapply();
398 BindingBase oldBinding = context.Binding;
399 context.Binding = binding;
401 targetProperty.BindingChanging?.Invoke(this, oldBinding, binding);
403 binding.Apply(BindingContext, this, targetProperty);
406 bool CanBeSetFromStyle(BindableProperty property)
408 var context = GetContext(property);
411 if ((context.Attributes & BindableContextAttributes.IsSetFromStyle) == BindableContextAttributes.IsSetFromStyle)
413 if ((context.Attributes & BindableContextAttributes.IsDefaultValue) == BindableContextAttributes.IsDefaultValue)
415 if ((context.Attributes & BindableContextAttributes.IsDefaultValueCreated) == BindableContextAttributes.IsDefaultValueCreated)
420 internal void SetDynamicResource(BindableProperty property, string key)
422 SetDynamicResource(property, key, false);
425 internal void SetDynamicResource(BindableProperty property, string key, bool fromStyle)
427 if (property == null)
428 throw new ArgumentNullException(nameof(property));
429 if (string.IsNullOrEmpty(key))
430 throw new ArgumentNullException(nameof(key));
431 if (fromStyle && !CanBeSetFromStyle(property))
434 var context = GetOrCreateContext(property);
436 context.Attributes |= BindableContextAttributes.IsDynamicResource;
438 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
440 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
442 OnSetDynamicResource(property, key);
445 internal void SetValue(BindableProperty property, object value, bool fromStyle)
447 SetValue(property, value, fromStyle, true);
450 internal void SetValueCore(BindablePropertyKey propertyKey, object value, SetValueFlags attributes = SetValueFlags.None)
452 SetValueCore(propertyKey.BindableProperty, value, attributes, SetValuePrivateFlags.None);
456 /// For internal use.
458 /// <param name="property">The BindableProperty on which to assign a value.</param>
459 /// <param name="value">The value to set</param>
460 /// <param name="attributes">The set value flag</param>
461 [EditorBrowsable(EditorBrowsableState.Never)]
462 public void SetValueCore(BindableProperty property, object value, SetValueFlags attributes = SetValueFlags.None)
464 SetValueCore(property, value, attributes, SetValuePrivateFlags.Default);
467 internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
469 bool checkAccess = (privateAttributes & SetValuePrivateFlags.CheckAccess) != 0;
470 bool manuallySet = (privateAttributes & SetValuePrivateFlags.ManuallySet) != 0;
471 bool silent = (privateAttributes & SetValuePrivateFlags.Silent) != 0;
472 bool fromStyle = (privateAttributes & SetValuePrivateFlags.FromStyle) != 0;
473 bool converted = (privateAttributes & SetValuePrivateFlags.Converted) != 0;
475 if (property == null)
476 throw new ArgumentNullException("property");
477 if (checkAccess && property.IsReadOnly)
479 Debug.WriteLine("Can not set the BindableProperty \"{0}\" because it is readonly.", property.PropertyName);
483 if (!converted && !property.TryConvert(ref value))
485 Console.WriteLine("SetValue", "Can not convert {0} to type '{1}'", value, property.ReturnType);
489 if (property.ValidateValue != null && !property.ValidateValue(this, value))
490 throw new ArgumentException("Value was an invalid value for " + property.PropertyName, "value");
492 if (property.CoerceValue != null)
493 value = property.CoerceValue(this, value);
495 BindablePropertyContext context = GetOrCreateContext(property);
497 context.Attributes |= BindableContextAttributes.IsManuallySet;
498 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
500 context.Attributes &= ~BindableContextAttributes.IsManuallySet;
503 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
504 // else omitted on purpose
506 bool currentlyApplying = _applying;
508 if ((context.Attributes & BindableContextAttributes.IsBeingSet) != 0)
510 Queue<SetValueArgs> delayQueue = context.DelayedSetters;
511 if (delayQueue == null)
512 context.DelayedSetters = delayQueue = new Queue<SetValueArgs>();
514 delayQueue.Enqueue(new SetValueArgs(property, context, value, currentlyApplying, attributes));
518 context.Attributes |= BindableContextAttributes.IsBeingSet;
519 SetValueActual(property, context, value, currentlyApplying, attributes, silent);
521 Queue<SetValueArgs> delayQueue = context.DelayedSetters;
522 if (delayQueue != null)
524 while (delayQueue.Count > 0)
526 SetValueArgs s = delayQueue.Dequeue();
527 SetValueActual(s.Property, s.Context, s.Value, s.CurrentlyApplying, s.Attributes);
530 context.DelayedSetters = null;
533 context.Attributes &= ~BindableContextAttributes.IsBeingSet;
537 internal void ApplyBindings(bool skipBindingContext, bool fromBindingContextChanged)
539 var prop = _properties.ToArray();
540 for (int i = 0, propLength = prop.Length; i < propLength; i++) {
541 BindablePropertyContext context = prop [i];
542 BindingBase binding = context.Binding;
546 if (skipBindingContext && ReferenceEquals(context.Property, BindingContextProperty))
549 binding.Unapply(fromBindingContextChanged: fromBindingContextChanged);
550 binding.Apply(BindingContext, this, context.Property, fromBindingContextChanged: fromBindingContextChanged);
554 static void BindingContextPropertyBindingChanging(BindableObject bindable, BindingBase oldBindingBase, BindingBase newBindingBase)
556 object context = bindable._inheritedContext;
557 var oldBinding = oldBindingBase as Binding;
558 var newBinding = newBindingBase as Binding;
560 if (context == null && oldBinding != null)
561 context = oldBinding.Context;
562 if (context != null && newBinding != null)
563 newBinding.Context = context;
566 static void BindingContextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
568 bindable._inheritedContext = null;
569 bindable.ApplyBindings(skipBindingContext: true, fromBindingContextChanged:true);
570 bindable.OnBindingContextChanged();
573 void ClearValue(BindableProperty property, bool fromStyle, bool checkAccess)
575 if (property == null)
576 throw new ArgumentNullException(nameof(property));
578 if (checkAccess && property.IsReadOnly)
579 throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
581 BindablePropertyContext bpcontext = GetContext(property);
582 if (bpcontext == null)
585 if (fromStyle && !CanBeSetFromStyle(property))
588 object original = bpcontext.Value;
590 object newValue = property.GetDefaultValue(this);
592 bool same = Equals(original, newValue);
595 property.PropertyChanging?.Invoke(this, original, newValue);
597 OnPropertyChanging(property.PropertyName);
600 bpcontext.Attributes &= ~BindableContextAttributes.IsManuallySet;
601 bpcontext.Value = newValue;
602 if (property.DefaultValueCreator == null)
603 bpcontext.Attributes |= BindableContextAttributes.IsDefaultValue;
605 bpcontext.Attributes |= BindableContextAttributes.IsDefaultValueCreated;
609 OnPropertyChanged(property.PropertyName);
610 property.PropertyChanged?.Invoke(this, original, newValue);
614 [MethodImpl(MethodImplOptions.AggressiveInlining)]
615 BindablePropertyContext CreateAndAddContext(BindableProperty property)
617 var context = new BindablePropertyContext { Property = property, Value = property.DefaultValueCreator != null ? property.DefaultValueCreator(this) : property.DefaultValue };
619 if (property.DefaultValueCreator == null)
620 context.Attributes = BindableContextAttributes.IsDefaultValue;
622 context.Attributes = BindableContextAttributes.IsDefaultValueCreated;
624 _properties.Add(context);
628 [MethodImpl(MethodImplOptions.AggressiveInlining)]
629 BindablePropertyContext GetContext(BindableProperty property)
631 List<BindablePropertyContext> properties = _properties;
633 for (var i = 0; i < properties.Count; i++)
635 BindablePropertyContext context = properties[i];
636 if (ReferenceEquals(context.Property, property))
643 [MethodImpl(MethodImplOptions.AggressiveInlining)]
644 BindablePropertyContext GetOrCreateContext(BindableProperty property)
646 BindablePropertyContext context = GetContext(property);
649 context = CreateAndAddContext(property);
655 void RemoveBinding(BindableProperty property, BindablePropertyContext context)
657 context.Binding.Unapply();
659 property.BindingChanging?.Invoke(this, context.Binding, null);
661 context.Binding = null;
664 void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess)
666 if (property == null)
667 throw new ArgumentNullException("property");
669 if (checkAccess && property.IsReadOnly)
670 throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
672 if (fromStyle && !CanBeSetFromStyle(property))
675 SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
676 (fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0));
679 void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
681 object original = context.Value;
682 bool raiseOnEqual = (attributes & SetValueFlags.RaiseOnEqual) != 0;
683 bool clearDynamicResources = (attributes & SetValueFlags.ClearDynamicResource) != 0;
684 bool clearOneWayBindings = (attributes & SetValueFlags.ClearOneWayBindings) != 0;
685 bool clearTwoWayBindings = (attributes & SetValueFlags.ClearTwoWayBindings) != 0;
687 bool same = ReferenceEquals(context.Property, BindingContextProperty) ? ReferenceEquals(value, original) : Equals(value, original);
688 if (!silent && (!same || raiseOnEqual))
690 property.PropertyChanging?.Invoke(this, original, value);
692 OnPropertyChanging(property.PropertyName);
695 if (!same || raiseOnEqual)
697 context.Value = value;
700 context.Attributes &= ~BindableContextAttributes.IsDefaultValue;
701 context.Attributes &= ~BindableContextAttributes.IsDefaultValueCreated;
703 if ((context.Attributes & BindableContextAttributes.IsDynamicResource) != 0 && clearDynamicResources)
704 RemoveDynamicResource(property);
706 BindingBase binding = context.Binding;
709 if (clearOneWayBindings && binding.GetRealizedMode(property) == BindingMode.OneWay || clearTwoWayBindings && binding.GetRealizedMode(property) == BindingMode.TwoWay)
711 RemoveBinding(property, context);
716 if (!silent && (!same || raiseOnEqual))
718 if (binding != null && !currentlyApplying)
725 OnPropertyChanged(property.PropertyName);
727 property.PropertyChanged?.Invoke(this, original, value);
732 enum BindableContextAttributes
734 IsManuallySet = 1 << 0,
736 IsDynamicResource = 1 << 2,
737 IsSetFromStyle = 1 << 3,
738 IsDefaultValue = 1 << 4,
739 IsDefaultValueCreated = 1 << 5,
742 class BindablePropertyContext
744 public BindableContextAttributes Attributes;
745 public BindingBase Binding;
746 public Queue<SetValueArgs> DelayedSetters;
747 public BindableProperty Property;
752 internal enum SetValuePrivateFlags
755 CheckAccess = 1 << 0,
757 ManuallySet = 1 << 2,
760 Default = CheckAccess
765 public readonly SetValueFlags Attributes;
766 public readonly BindablePropertyContext Context;
767 public readonly bool CurrentlyApplying;
768 public readonly BindableProperty Property;
769 public readonly object Value;
771 public SetValueArgs(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes)
776 CurrentlyApplying = currentlyApplying;
777 Attributes = attributes;
783 namespace Tizen.NUI.Internals
786 /// SetValueFlags. For internal use.
789 [EditorBrowsable(EditorBrowsableState.Never)]
790 public enum SetValueFlags
798 /// Clear OneWay bindings.
800 ClearOneWayBindings = 1 << 0,
803 /// Clear TwoWay bindings.
805 ClearTwoWayBindings = 1 << 1,
808 /// Clear dynamic resource.
810 ClearDynamicResource = 1 << 2,
815 RaiseOnEqual = 1 << 3