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 public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
17 /// Implements the bound property whose interface is provided by the BindingContext property.
19 public static readonly BindableProperty BindingContextProperty =
20 BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object),
21 BindingMode.OneWay, null, BindingContextPropertyChanged, null, null, BindingContextPropertyBindingChanging);
23 readonly List<BindablePropertyContext> _properties = new List<BindablePropertyContext>(4);
26 object _inheritedContext;
29 /// Gets or sets object that contains the properties that will be targeted by the bound properties that belong to this BindableObject.
31 public object BindingContext
33 get { return _inheritedContext ?? GetValue(BindingContextProperty); }
34 set { SetValue(BindingContextProperty, value); }
37 void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key)
39 SetDynamicResource(property, key, false);
43 /// Raised when a property has changed.
45 public event PropertyChangedEventHandler PropertyChanged;
48 /// Raised whenever the BindingContext property changes.
50 public event EventHandler BindingContextChanged;
52 internal void ClearValue(BindableProperty property, bool fromStyle)
54 ClearValue(property, fromStyle: fromStyle, checkAccess: true);
58 /// Clears any value set by Xamarin.Forms.BindableObject.SetValue.
60 /// <param name="property">The BindableProperty to clear</param>
61 public void ClearValue(BindableProperty property)
63 ClearValue(property, fromStyle: false, checkAccess: true);
67 /// Clears any value set by Xamarin.Forms.BindableObject.SetValue for the property that is identified by propertyKey.
69 /// <param name="propertyKey">The BindablePropertyKey that identifies the BindableProperty to clear.</param>
70 public void ClearValue(BindablePropertyKey propertyKey)
72 if (propertyKey == null)
73 throw new ArgumentNullException("propertyKey");
75 ClearValue(propertyKey.BindableProperty, fromStyle:false, checkAccess: false);
79 /// Return true if the target property exists and has been set.
81 /// <param name="targetProperty">The target property</param>
82 /// <returns>return true if the target property exists and has been set</returns>
83 public bool IsSet(BindableProperty targetProperty)
85 if (targetProperty == null)
86 throw new ArgumentNullException(nameof(targetProperty));
88 var bpcontext = GetContext(targetProperty);
89 return bpcontext != null
90 && (bpcontext.Attributes & BindableContextAttributes.IsDefaultValue) == 0;
94 /// Returns the value that is contained the BindableProperty.
96 /// <param name="property">The BindableProperty for which to get the value.</param>
97 /// <returns>The value that is contained the BindableProperty</returns>
98 public object GetValue(BindableProperty property)
100 if (property == null)
101 throw new ArgumentNullException("property");
103 BindablePropertyContext context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
106 return property.DefaultValue;
108 return context.Value;
112 /// Raised when a property is about to change.
114 public event PropertyChangingEventHandler PropertyChanging;
117 /// Removes a previously set binding.
119 /// <param name="property">The BindableProperty from which to remove bindings.</param>
120 public void RemoveBinding(BindableProperty property)
122 if (property == null)
123 throw new ArgumentNullException("property");
125 BindablePropertyContext context = GetContext(property);
126 if (context == null || context.Binding == null)
129 RemoveBinding(property, context);
133 /// Assigns a binding to a property.
135 /// <param name="targetProperty">The BindableProperty on which to set a binding.</param>
136 /// <param name="binding">The binding to set.</param>
137 public void SetBinding(BindableProperty targetProperty, BindingBase binding)
139 SetBinding(targetProperty, binding, false);
143 /// Sets the value of the specified property.
145 /// <param name="property">The BindableProperty on which to assign a value.</param>
146 /// <param name="value">The value to set.</param>
147 public void SetValue(BindableProperty property, object value)
149 SetValue(property, value, false, true);
153 /// Sets the value of the propertyKey.
155 /// <param name="propertyKey">The BindablePropertyKey on which to assign a value.</param>
156 /// <param name="value">The value to set.</param>
157 public void SetValue(BindablePropertyKey propertyKey, object value)
159 if (propertyKey == null)
160 throw new ArgumentNullException("propertyKey");
162 SetValue(propertyKey.BindableProperty, value, false, false);
166 /// Set the inherited context to a neated element.
168 /// <param name="bindable">The object on which to set the inherited binding context.</param>
169 /// <param name="value">The inherited context to set.</param>
170 [EditorBrowsable(EditorBrowsableState.Never)]
171 public static void SetInheritedBindingContext(BindableObject bindable, object value)
173 BindablePropertyContext bpContext = bindable.GetContext(BindingContextProperty);
174 if (bpContext != null && ((bpContext.Attributes & BindableContextAttributes.IsManuallySet) != 0))
177 object oldContext = bindable._inheritedContext;
179 if (ReferenceEquals(oldContext, value))
182 if (bpContext != null && oldContext == null)
183 oldContext = bpContext.Value;
185 if (bpContext != null && bpContext.Binding != null)
187 bpContext.Binding.Context = value;
188 bindable._inheritedContext = null;
192 bindable._inheritedContext = value;
195 bindable.ApplyBindings(skipBindingContext:false, fromBindingContextChanged:true);
196 bindable.OnBindingContextChanged();
200 /// Apply the bindings to BindingContext.
202 protected void ApplyBindings()
204 ApplyBindings(skipBindingContext: false, fromBindingContextChanged: false);
208 /// Override this method to execute an action when the BindingContext changes.
210 protected virtual void OnBindingContextChanged()
212 BindingContextChanged?.Invoke(this, EventArgs.Empty);
216 /// Call this method from a child class to notify that a change happened on a property.
218 /// <param name="propertyName">The name of the property that changed.</param>
219 protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
220 => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
223 /// Call this method from a child class to notify that a change is going to happen on a property.
225 /// <param name="propertyName">The name of the property that is changing.</param>
226 protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null)
227 => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
230 /// Unapplies all previously set bindings.
232 protected void UnapplyBindings()
234 for (int i = 0, _propertiesCount = _properties.Count; i < _propertiesCount; i++) {
235 BindablePropertyContext context = _properties [i];
236 if (context.Binding == null)
239 context.Binding.Unapply();
243 internal bool GetIsBound(BindableProperty targetProperty)
245 if (targetProperty == null)
246 throw new ArgumentNullException("targetProperty");
248 BindablePropertyContext bpcontext = GetContext(targetProperty);
249 return bpcontext != null && bpcontext.Binding != null;
253 /// Returns the value that is contained the BindableProperty.
255 /// <param name="property0">The BindableProperty instance.</param>
256 /// <param name="property1">The BindableProperty instance.</param>
257 /// <returns>The value that is contained the BindableProperty</returns>
258 [EditorBrowsable(EditorBrowsableState.Never)]
259 public object[] GetValues(BindableProperty property0, BindableProperty property1)
261 var values = new object[2];
263 for (var i = 0; i < _properties.Count; i++)
265 BindablePropertyContext context = _properties[i];
267 if (ReferenceEquals(context.Property, property0))
269 values[0] = context.Value;
272 else if (ReferenceEquals(context.Property, property1))
274 values[1] = context.Value;
278 if (property0 == null && property1 == null)
282 if (!ReferenceEquals(property0, null))
283 values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
284 if (!ReferenceEquals(property1, null))
285 values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
291 /// Returns the value that is contained the BindableProperty.
293 /// <param name="property0">The BindableProperty instance.</param>
294 /// <param name="property1">The BindableProperty instance.</param>
295 /// <param name="property2">The BindableProperty instance.</param>
296 /// <returns>The value that is contained the BindableProperty</returns>
297 [EditorBrowsable(EditorBrowsableState.Never)]
298 public object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2)
300 var values = new object[3];
302 for (var i = 0; i < _properties.Count; i++)
304 BindablePropertyContext context = _properties[i];
306 if (ReferenceEquals(context.Property, property0))
308 values[0] = context.Value;
311 else if (ReferenceEquals(context.Property, property1))
313 values[1] = context.Value;
316 else if (ReferenceEquals(context.Property, property2))
318 values[2] = context.Value;
322 if (property0 == null && property1 == null && property2 == null)
326 if (!ReferenceEquals(property0, null))
327 values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
328 if (!ReferenceEquals(property1, null))
329 values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
330 if (!ReferenceEquals(property2, null))
331 values[2] = property2.DefaultValueCreator == null ? property2.DefaultValue : CreateAndAddContext(property2).Value;
337 /// Returns the value that is contained the BindableProperty.
339 /// <param name="properties">The array of the BindableProperty instances</param>
340 /// <returns>The values that is contained the BindableProperty instances.</returns>
341 [EditorBrowsable(EditorBrowsableState.Never)]
342 internal object[] GetValues(params BindableProperty[] properties)
344 var values = new object[properties.Length];
345 for (var i = 0; i < _properties.Count; i++) {
346 var context = _properties[i];
347 var index = properties.IndexOf(context.Property);
350 values[index] = context.Value;
352 for (var i = 0; i < values.Length; i++) {
353 if (!ReferenceEquals(values[i], null))
355 values[i] = properties[i].DefaultValueCreator == null ? properties[i].DefaultValue : CreateAndAddContext(properties[i]).Value;
360 internal virtual void OnRemoveDynamicResource(BindableProperty property)
364 internal virtual void OnSetDynamicResource(BindableProperty property, string key)
368 internal void RemoveDynamicResource(BindableProperty property)
370 if (property == null)
371 throw new ArgumentNullException("property");
373 OnRemoveDynamicResource(property);
374 BindablePropertyContext context = GetOrCreateContext(property);
375 context.Attributes &= ~BindableContextAttributes.IsDynamicResource;
378 internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle)
380 if (targetProperty == null)
381 throw new ArgumentNullException("targetProperty");
383 throw new ArgumentNullException("binding");
385 if (fromStyle && !CanBeSetFromStyle(targetProperty))
388 var context = GetOrCreateContext(targetProperty);
390 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
392 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
394 if (context.Binding != null)
395 context.Binding.Unapply();
397 BindingBase oldBinding = context.Binding;
398 context.Binding = binding;
400 targetProperty.BindingChanging?.Invoke(this, oldBinding, binding);
402 binding.Apply(BindingContext, this, targetProperty);
405 bool CanBeSetFromStyle(BindableProperty property)
407 var context = GetContext(property);
410 if ((context.Attributes & BindableContextAttributes.IsSetFromStyle) == BindableContextAttributes.IsSetFromStyle)
412 if ((context.Attributes & BindableContextAttributes.IsDefaultValue) == BindableContextAttributes.IsDefaultValue)
414 if ((context.Attributes & BindableContextAttributes.IsDefaultValueCreated) == BindableContextAttributes.IsDefaultValueCreated)
419 internal void SetDynamicResource(BindableProperty property, string key)
421 SetDynamicResource(property, key, false);
424 internal void SetDynamicResource(BindableProperty property, string key, bool fromStyle)
426 if (property == null)
427 throw new ArgumentNullException(nameof(property));
428 if (string.IsNullOrEmpty(key))
429 throw new ArgumentNullException(nameof(key));
430 if (fromStyle && !CanBeSetFromStyle(property))
433 var context = GetOrCreateContext(property);
435 context.Attributes |= BindableContextAttributes.IsDynamicResource;
437 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
439 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
441 OnSetDynamicResource(property, key);
444 internal void SetValue(BindableProperty property, object value, bool fromStyle)
446 SetValue(property, value, fromStyle, true);
449 internal void SetValueCore(BindablePropertyKey propertyKey, object value, SetValueFlags attributes = SetValueFlags.None)
451 SetValueCore(propertyKey.BindableProperty, value, attributes, SetValuePrivateFlags.None);
455 /// For internal use.
457 /// <param name="property">The BindableProperty on which to assign a value.</param>
458 /// <param name="value">The value to set</param>
459 /// <param name="attributes">The set value flag</param>
460 [EditorBrowsable(EditorBrowsableState.Never)]
461 public void SetValueCore(BindableProperty property, object value, SetValueFlags attributes = SetValueFlags.None)
463 SetValueCore(property, value, attributes, SetValuePrivateFlags.Default);
466 internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
468 bool checkAccess = (privateAttributes & SetValuePrivateFlags.CheckAccess) != 0;
469 bool manuallySet = (privateAttributes & SetValuePrivateFlags.ManuallySet) != 0;
470 bool silent = (privateAttributes & SetValuePrivateFlags.Silent) != 0;
471 bool fromStyle = (privateAttributes & SetValuePrivateFlags.FromStyle) != 0;
472 bool converted = (privateAttributes & SetValuePrivateFlags.Converted) != 0;
474 if (property == null)
475 throw new ArgumentNullException("property");
476 if (checkAccess && property.IsReadOnly)
478 Debug.WriteLine("Can not set the BindableProperty \"{0}\" because it is readonly.", property.PropertyName);
482 if (!converted && !property.TryConvert(ref value))
484 Console.WriteLine("SetValue", "Can not convert {0} to type '{1}'", value, property.ReturnType);
488 if (property.ValidateValue != null && !property.ValidateValue(this, value))
489 throw new ArgumentException("Value was an invalid value for " + property.PropertyName, "value");
491 if (property.CoerceValue != null)
492 value = property.CoerceValue(this, value);
494 BindablePropertyContext context = GetOrCreateContext(property);
496 context.Attributes |= BindableContextAttributes.IsManuallySet;
497 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
499 context.Attributes &= ~BindableContextAttributes.IsManuallySet;
502 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
503 // else omitted on purpose
505 bool currentlyApplying = _applying;
507 if ((context.Attributes & BindableContextAttributes.IsBeingSet) != 0)
509 Queue<SetValueArgs> delayQueue = context.DelayedSetters;
510 if (delayQueue == null)
511 context.DelayedSetters = delayQueue = new Queue<SetValueArgs>();
513 delayQueue.Enqueue(new SetValueArgs(property, context, value, currentlyApplying, attributes));
517 context.Attributes |= BindableContextAttributes.IsBeingSet;
518 SetValueActual(property, context, value, currentlyApplying, attributes, silent);
520 Queue<SetValueArgs> delayQueue = context.DelayedSetters;
521 if (delayQueue != null)
523 while (delayQueue.Count > 0)
525 SetValueArgs s = delayQueue.Dequeue();
526 SetValueActual(s.Property, s.Context, s.Value, s.CurrentlyApplying, s.Attributes);
529 context.DelayedSetters = null;
532 context.Attributes &= ~BindableContextAttributes.IsBeingSet;
536 internal void ApplyBindings(bool skipBindingContext, bool fromBindingContextChanged)
538 var prop = _properties.ToArray();
539 for (int i = 0, propLength = prop.Length; i < propLength; i++) {
540 BindablePropertyContext context = prop [i];
541 BindingBase binding = context.Binding;
545 if (skipBindingContext && ReferenceEquals(context.Property, BindingContextProperty))
548 binding.Unapply(fromBindingContextChanged: fromBindingContextChanged);
549 binding.Apply(BindingContext, this, context.Property, fromBindingContextChanged: fromBindingContextChanged);
553 static void BindingContextPropertyBindingChanging(BindableObject bindable, BindingBase oldBindingBase, BindingBase newBindingBase)
555 object context = bindable._inheritedContext;
556 var oldBinding = oldBindingBase as Binding;
557 var newBinding = newBindingBase as Binding;
559 if (context == null && oldBinding != null)
560 context = oldBinding.Context;
561 if (context != null && newBinding != null)
562 newBinding.Context = context;
565 static void BindingContextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
567 bindable._inheritedContext = null;
568 bindable.ApplyBindings(skipBindingContext: true, fromBindingContextChanged:true);
569 bindable.OnBindingContextChanged();
572 void ClearValue(BindableProperty property, bool fromStyle, bool checkAccess)
574 if (property == null)
575 throw new ArgumentNullException(nameof(property));
577 if (checkAccess && property.IsReadOnly)
578 throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
580 BindablePropertyContext bpcontext = GetContext(property);
581 if (bpcontext == null)
584 if (fromStyle && !CanBeSetFromStyle(property))
587 object original = bpcontext.Value;
589 object newValue = property.GetDefaultValue(this);
591 bool same = Equals(original, newValue);
594 property.PropertyChanging?.Invoke(this, original, newValue);
596 OnPropertyChanging(property.PropertyName);
599 bpcontext.Attributes &= ~BindableContextAttributes.IsManuallySet;
600 bpcontext.Value = newValue;
601 if (property.DefaultValueCreator == null)
602 bpcontext.Attributes |= BindableContextAttributes.IsDefaultValue;
604 bpcontext.Attributes |= BindableContextAttributes.IsDefaultValueCreated;
608 OnPropertyChanged(property.PropertyName);
609 property.PropertyChanged?.Invoke(this, original, newValue);
613 [MethodImpl(MethodImplOptions.AggressiveInlining)]
614 BindablePropertyContext CreateAndAddContext(BindableProperty property)
616 var context = new BindablePropertyContext { Property = property, Value = property.DefaultValueCreator != null ? property.DefaultValueCreator(this) : property.DefaultValue };
618 if (property.DefaultValueCreator == null)
619 context.Attributes = BindableContextAttributes.IsDefaultValue;
621 context.Attributes = BindableContextAttributes.IsDefaultValueCreated;
623 _properties.Add(context);
627 [MethodImpl(MethodImplOptions.AggressiveInlining)]
628 BindablePropertyContext GetContext(BindableProperty property)
630 List<BindablePropertyContext> properties = _properties;
632 for (var i = 0; i < properties.Count; i++)
634 BindablePropertyContext context = properties[i];
635 if (ReferenceEquals(context.Property, property))
642 [MethodImpl(MethodImplOptions.AggressiveInlining)]
643 BindablePropertyContext GetOrCreateContext(BindableProperty property)
645 BindablePropertyContext context = GetContext(property);
648 context = CreateAndAddContext(property);
654 void RemoveBinding(BindableProperty property, BindablePropertyContext context)
656 context.Binding.Unapply();
658 property.BindingChanging?.Invoke(this, context.Binding, null);
660 context.Binding = null;
663 void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess)
665 if (property == null)
666 throw new ArgumentNullException("property");
668 if (checkAccess && property.IsReadOnly)
669 throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
671 if (fromStyle && !CanBeSetFromStyle(property))
674 SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
675 (fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0));
678 void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
680 object original = context.Value;
681 bool raiseOnEqual = (attributes & SetValueFlags.RaiseOnEqual) != 0;
682 bool clearDynamicResources = (attributes & SetValueFlags.ClearDynamicResource) != 0;
683 bool clearOneWayBindings = (attributes & SetValueFlags.ClearOneWayBindings) != 0;
684 bool clearTwoWayBindings = (attributes & SetValueFlags.ClearTwoWayBindings) != 0;
686 bool same = ReferenceEquals(context.Property, BindingContextProperty) ? ReferenceEquals(value, original) : Equals(value, original);
687 if (!silent && (!same || raiseOnEqual))
689 property.PropertyChanging?.Invoke(this, original, value);
691 OnPropertyChanging(property.PropertyName);
694 if (!same || raiseOnEqual)
696 context.Value = value;
699 context.Attributes &= ~BindableContextAttributes.IsDefaultValue;
700 context.Attributes &= ~BindableContextAttributes.IsDefaultValueCreated;
702 if ((context.Attributes & BindableContextAttributes.IsDynamicResource) != 0 && clearDynamicResources)
703 RemoveDynamicResource(property);
705 BindingBase binding = context.Binding;
708 if (clearOneWayBindings && binding.GetRealizedMode(property) == BindingMode.OneWay || clearTwoWayBindings && binding.GetRealizedMode(property) == BindingMode.TwoWay)
710 RemoveBinding(property, context);
715 if (!silent && (!same || raiseOnEqual))
717 if (binding != null && !currentlyApplying)
724 OnPropertyChanged(property.PropertyName);
726 property.PropertyChanged?.Invoke(this, original, value);
731 enum BindableContextAttributes
733 IsManuallySet = 1 << 0,
735 IsDynamicResource = 1 << 2,
736 IsSetFromStyle = 1 << 3,
737 IsDefaultValue = 1 << 4,
738 IsDefaultValueCreated = 1 << 5,
741 class BindablePropertyContext
743 public BindableContextAttributes Attributes;
744 public BindingBase Binding;
745 public Queue<SetValueArgs> DelayedSetters;
746 public BindableProperty Property;
751 internal enum SetValuePrivateFlags
754 CheckAccess = 1 << 0,
756 ManuallySet = 1 << 2,
759 Default = CheckAccess
764 public readonly SetValueFlags Attributes;
765 public readonly BindablePropertyContext Context;
766 public readonly bool CurrentlyApplying;
767 public readonly BindableProperty Property;
768 public readonly object Value;
770 public SetValueArgs(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes)
775 CurrentlyApplying = currentlyApplying;
776 Attributes = attributes;
782 namespace Tizen.NUI.Internals
785 /// SetValueFlags. For internal use.
788 [EditorBrowsable(EditorBrowsableState.Never)]
789 public enum SetValueFlags
797 /// Clear OneWay bindings.
799 ClearOneWayBindings = 1 << 0,
802 /// Clear TwoWay bindings.
804 ClearTwoWayBindings = 1 << 1,
807 /// Clear dynamic resource.
809 ClearDynamicResource = 1 << 2,
814 RaiseOnEqual = 1 << 3